pec2 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- Mode: python -*-
3
-
4
- # Copyright (c) 2009-2012, Andrew McNabb
5
-
6
- """Sends the password over a socket to askpass.
7
- """
8
-
9
- import errno
10
- import getpass
11
- import os
12
- import socket
13
- import sys
14
- import tempfile
15
- import textwrap
16
-
17
- from psshlib import psshutil
18
-
19
-
20
- class PasswordServer(object):
21
- """Listens on a UNIX domain socket for password requests."""
22
- def __init__(self):
23
- self.sock = None
24
- self.tempdir = None
25
- self.address = None
26
- self.socketmap = {}
27
- self.buffermap = {}
28
-
29
- def start(self, iomap, backlog):
30
- """Prompts for the password, creates a socket, and starts listening.
31
-
32
- The specified backlog should be the max number of clients connecting
33
- at once.
34
- """
35
- message = ('Warning: do not enter your password if anyone else has'
36
- ' superuser privileges or access to your account.')
37
- print(textwrap.fill(message))
38
-
39
- self.password = getpass.getpass()
40
-
41
- # Note that according to the docs for mkdtemp, "The directory is
42
- # readable, writable, and searchable only by the creating user."
43
- self.tempdir = tempfile.mkdtemp(prefix='pssh.')
44
- self.address = os.path.join(self.tempdir, 'pssh_askpass_socket')
45
- self.sock = socket.socket(socket.AF_UNIX)
46
- psshutil.set_cloexec(self.sock)
47
- self.sock.bind(self.address)
48
- self.sock.listen(backlog)
49
- iomap.register_read(self.sock.fileno(), self.handle_listen)
50
-
51
- def handle_listen(self, fd, iomap):
52
- try:
53
- conn = self.sock.accept()[0]
54
- except socket.error:
55
- _, e, _ = sys.exc_info()
56
- number = e.args[0]
57
- if number == errno.EINTR:
58
- return
59
- else:
60
- # TODO: print an error message here?
61
- self.sock.close()
62
- self.sock = None
63
- fd = conn.fileno()
64
- iomap.register_write(fd, self.handle_write)
65
- self.socketmap[fd] = conn
66
- self.buffermap[fd] = self.password
67
-
68
- def handle_write(self, fd, iomap):
69
- buffer = self.buffermap[fd]
70
- conn = self.socketmap[fd]
71
- try:
72
- bytes_written = conn.send(buffer)
73
- except socket.error:
74
- _, e, _ = sys.exc_info()
75
- number = e.args[0]
76
- if number == errno.EINTR:
77
- return
78
- else:
79
- self.close_socket(fd, iomap)
80
-
81
- buffer = buffer[bytes_written:]
82
- if buffer:
83
- self.buffermap[fd] = buffer
84
- else:
85
- self.close_socket(fd, iomap)
86
-
87
- def close_socket(self, fd, iomap):
88
- iomap.unregister(fd)
89
- self.socketmap[fd].close()
90
- del self.socketmap[fd]
91
- del self.buffermap[fd]
92
-
93
- def __del__(self):
94
- if self.sock:
95
- self.sock.close()
96
- self.sock = None
97
- if self.address:
98
- os.remove(self.address)
99
- if self.tempdir:
100
- os.rmdir(self.tempdir)
101
-
data/exe/psshlib/cli.py DELETED
@@ -1,110 +0,0 @@
1
- # Copyright (c) 2009-2012, Andrew McNabb
2
- # Copyright (c) 2003-2008, Brent N. Chun
3
-
4
- import optparse
5
- import os
6
- import shlex
7
- import sys
8
- import textwrap
9
- import version
10
-
11
- _DEFAULT_PARALLELISM = 32
12
- _DEFAULT_TIMEOUT = 0 # "infinity" by default
13
-
14
-
15
- def common_parser():
16
- """
17
- Create a basic OptionParser with arguments common to all pssh programs.
18
- """
19
- # The "resolve" conflict handler avoids errors from the hosts option
20
- # conflicting with the help option.
21
- parser = optparse.OptionParser(conflict_handler='resolve',
22
- version=version.VERSION)
23
- # Ensure that options appearing after the command are sent to ssh.
24
- parser.disable_interspersed_args()
25
- parser.epilog = "Example: pssh -h nodes.txt -l irb2 -o /tmp/foo uptime"
26
-
27
- parser.add_option('-h', '--hosts', dest='host_files', action='append',
28
- metavar='HOST_FILE',
29
- help='hosts file (each line "[user@]host[:port]")')
30
- parser.add_option('-H', '--host', dest='host_strings', action='append',
31
- metavar='HOST_STRING',
32
- help='additional host entries ("[user@]host[:port]")')
33
- parser.add_option('-l', '--user', dest='user',
34
- help='username (OPTIONAL)')
35
- parser.add_option('-p', '--par', dest='par', type='int',
36
- help='max number of parallel threads (OPTIONAL)')
37
- parser.add_option('-o', '--outdir', dest='outdir',
38
- help='output directory for stdout files (OPTIONAL)')
39
- parser.add_option('-e', '--errdir', dest='errdir',
40
- help='output directory for stderr files (OPTIONAL)')
41
- parser.add_option('-t', '--timeout', dest='timeout', type='int',
42
- help='timeout (secs) (0 = no timeout) per host (OPTIONAL)')
43
- parser.add_option('-O', '--option', dest='options', action='append',
44
- metavar='OPTION', help='SSH option (OPTIONAL)')
45
- parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
46
- help='turn on warning and diagnostic messages (OPTIONAL)')
47
- parser.add_option('-A', '--askpass', dest='askpass', action='store_true',
48
- help='Ask for a password (OPTIONAL)')
49
- parser.add_option('-x', '--extra-args', action='callback', type='string',
50
- metavar='ARGS', callback=shlex_append, dest='extra',
51
- help='Extra command-line arguments, with processing for '
52
- 'spaces, quotes, and backslashes')
53
- parser.add_option('-X', '--extra-arg', dest='extra', action='append',
54
- metavar='ARG', help='Extra command-line argument')
55
-
56
- return parser
57
-
58
-
59
- def common_defaults(**kwargs):
60
- defaults = dict(par=_DEFAULT_PARALLELISM, timeout=_DEFAULT_TIMEOUT)
61
- defaults.update(**kwargs)
62
- envvars = [('user', 'PSSH_USER'),
63
- ('par', 'PSSH_PAR'),
64
- ('outdir', 'PSSH_OUTDIR'),
65
- ('errdir', 'PSSH_ERRDIR'),
66
- ('timeout', 'PSSH_TIMEOUT'),
67
- ('verbose', 'PSSH_VERBOSE'),
68
- ('print_out', 'PSSH_PRINT'),
69
- ('askpass', 'PSSH_ASKPASS'),
70
- ('inline', 'PSSH_INLINE'),
71
- ('recursive', 'PSSH_RECURSIVE'),
72
- ('archive', 'PSSH_ARCHIVE'),
73
- ('compress', 'PSSH_COMPRESS'),
74
- ('localdir', 'PSSH_LOCALDIR'),
75
- ]
76
- for option, var, in envvars:
77
- value = os.getenv(var)
78
- if value:
79
- defaults[option] = value
80
-
81
- value = os.getenv('PSSH_OPTIONS')
82
- if value:
83
- defaults['options'] = [value]
84
-
85
- value = os.getenv('PSSH_HOSTS')
86
- if value:
87
- message1 = ('Warning: the PSSH_HOSTS environment variable is '
88
- 'deprecated. Please use the "-h" option instead, and consider '
89
- 'creating aliases for convenience. For example:')
90
- message2 = " alias pssh_abc='pssh -h /path/to/hosts_abc'"
91
- sys.stderr.write(textwrap.fill(message1))
92
- sys.stderr.write('\n')
93
- sys.stderr.write(message2)
94
- sys.stderr.write('\n')
95
- defaults['host_files'] = [value]
96
-
97
- return defaults
98
-
99
-
100
- def shlex_append(option, opt_str, value, parser):
101
- """An optparse callback similar to the append action.
102
-
103
- The given value is processed with shlex, and the resulting list is
104
- concatenated to the option's dest list.
105
- """
106
- lst = getattr(parser.values, option.dest)
107
- if lst is None:
108
- lst = []
109
- setattr(parser.values, option.dest, lst)
110
- lst.extend(shlex.split(value))
data/exe/psshlib/color.py DELETED
@@ -1,39 +0,0 @@
1
- # Copyright (c) 2009-2012, Andrew McNabb
2
- # Copyright (c) 2003-2008, Brent N. Chun
3
-
4
- def with_color(string, fg, bg=49):
5
- '''Given foreground/background ANSI color codes, return a string that,
6
- when printed, will format the supplied string using the supplied colors.
7
- '''
8
- return "\x1b[%dm\x1b[%dm%s\x1b[39m\x1b[49m" % (fg, bg, string)
9
-
10
- def B(string):
11
- '''Returns a string that, when printed, will display the supplied string
12
- in ANSI bold.
13
- '''
14
- return "\x1b[1m%s\x1b[22m" % string
15
-
16
- def r(string): return with_color(string, 31) # Red
17
- def g(string): return with_color(string, 32) # Green
18
- def y(string): return with_color(string, 33) # Yellow
19
- def b(string): return with_color(string, 34) # Blue
20
- def m(string): return with_color(string, 35) # Magenta
21
- def c(string): return with_color(string, 36) # Cyan
22
- def w(string): return with_color(string, 37) # White
23
-
24
- #following from Python cookbook, #475186
25
- def has_colors(stream):
26
- '''Returns boolean indicating whether or not the supplied stream supports
27
- ANSI color.
28
- '''
29
- if not hasattr(stream, "isatty"):
30
- return False
31
- if not stream.isatty():
32
- return False # auto color only on TTYs
33
- try:
34
- import curses
35
- curses.setupterm()
36
- return curses.tigetnum("colors") > 2
37
- except:
38
- # guess false in case of error
39
- return False
@@ -1,345 +0,0 @@
1
- # Copyright (c) 2009-2012, Andrew McNabb
2
-
3
- from errno import EINTR
4
- import os
5
- import select
6
- import signal
7
- import sys
8
- import threading
9
-
10
- try:
11
- import queue
12
- except ImportError:
13
- import Queue as queue
14
-
15
- from psshlib.askpass_server import PasswordServer
16
- from psshlib import psshutil
17
-
18
- READ_SIZE = 1 << 16
19
-
20
-
21
- class FatalError(RuntimeError):
22
- """A fatal error in the PSSH Manager."""
23
- pass
24
-
25
-
26
- class Manager(object):
27
- """Executes tasks concurrently.
28
-
29
- Tasks are added with add_task() and executed in parallel with run().
30
- Returns a list of the exit statuses of the processes.
31
-
32
- Arguments:
33
- limit: Maximum number of commands running at once.
34
- timeout: Maximum allowed execution time in seconds.
35
- """
36
- def __init__(self, opts):
37
- self.limit = opts.par
38
- self.timeout = opts.timeout
39
- self.askpass = opts.askpass
40
- self.outdir = opts.outdir
41
- self.errdir = opts.errdir
42
- self.iomap = IOMap()
43
-
44
- self.taskcount = 0
45
- self.tasks = []
46
- self.running = []
47
- self.done = []
48
-
49
- self.askpass_socket = None
50
-
51
- def run(self):
52
- """Processes tasks previously added with add_task."""
53
- try:
54
- if self.outdir or self.errdir:
55
- writer = Writer(self.outdir, self.errdir)
56
- writer.start()
57
- else:
58
- writer = None
59
-
60
- if self.askpass:
61
- pass_server = PasswordServer()
62
- pass_server.start(self.iomap, self.limit)
63
- self.askpass_socket = pass_server.address
64
-
65
- self.set_sigchld_handler()
66
-
67
- try:
68
- self.update_tasks(writer)
69
- wait = None
70
- while self.running or self.tasks:
71
- # Opt for efficiency over subsecond timeout accuracy.
72
- if wait is None or wait < 1:
73
- wait = 1
74
- self.iomap.poll(wait)
75
- self.update_tasks(writer)
76
- wait = self.check_timeout()
77
- except KeyboardInterrupt:
78
- # This exception handler tries to clean things up and prints
79
- # out a nice status message for each interrupted host.
80
- self.interrupted()
81
-
82
- except KeyboardInterrupt:
83
- # This exception handler doesn't print out any fancy status
84
- # information--it just stops.
85
- pass
86
-
87
- if writer:
88
- writer.signal_quit()
89
- writer.join()
90
-
91
- statuses = [task.exitstatus for task in self.done]
92
- return statuses
93
-
94
- def clear_sigchld_handler(self):
95
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
96
-
97
- def set_sigchld_handler(self):
98
- # TODO: find out whether set_wakeup_fd still works if the default
99
- # signal handler is used (I'm pretty sure it doesn't work if the
100
- # signal is ignored).
101
- signal.signal(signal.SIGCHLD, self.handle_sigchld)
102
- # This should keep reads and writes from getting EINTR.
103
- if hasattr(signal, 'siginterrupt'):
104
- signal.siginterrupt(signal.SIGCHLD, False)
105
-
106
- def handle_sigchld(self, number, frame):
107
- """Apparently we need a sigchld handler to make set_wakeup_fd work."""
108
- # Write to the signal pipe (only for Python <2.5, where the
109
- # set_wakeup_fd method doesn't exist).
110
- if self.iomap.wakeup_writefd:
111
- os.write(self.iomap.wakeup_writefd, '\0')
112
- for task in self.running:
113
- if task.proc:
114
- task.proc.poll()
115
- # Apparently some UNIX systems automatically resent the SIGCHLD
116
- # handler to SIG_DFL. Reset it just in case.
117
- self.set_sigchld_handler()
118
-
119
- def add_task(self, task):
120
- """Adds a Task to be processed with run()."""
121
- self.tasks.append(task)
122
-
123
- def update_tasks(self, writer):
124
- """Reaps tasks and starts as many new ones as allowed."""
125
- # Mask signals to work around a Python bug:
126
- # http://bugs.python.org/issue1068268
127
- # Since sigprocmask isn't in the stdlib, clear the SIGCHLD handler.
128
- # Since signals are masked, reap_tasks needs to be called once for
129
- # each loop.
130
- keep_running = True
131
- while keep_running:
132
- self.clear_sigchld_handler()
133
- self._start_tasks_once(writer)
134
- self.set_sigchld_handler()
135
- keep_running = self.reap_tasks()
136
-
137
- def _start_tasks_once(self, writer):
138
- """Starts tasks once.
139
-
140
- Due to http://bugs.python.org/issue1068268, signals must be masked
141
- when this method is called.
142
- """
143
- while 0 < len(self.tasks) and len(self.running) < self.limit:
144
- task = self.tasks.pop(0)
145
- self.running.append(task)
146
- task.start(self.taskcount, self.iomap, writer, self.askpass_socket)
147
- self.taskcount += 1
148
-
149
- def reap_tasks(self):
150
- """Checks to see if any tasks have terminated.
151
-
152
- After cleaning up, returns the number of tasks that finished.
153
- """
154
- still_running = []
155
- finished_count = 0
156
- for task in self.running:
157
- if task.running():
158
- still_running.append(task)
159
- else:
160
- self.finished(task)
161
- finished_count += 1
162
- self.running = still_running
163
- return finished_count
164
-
165
- def check_timeout(self):
166
- """Kills timed-out processes and returns the lowest time left."""
167
- if self.timeout <= 0:
168
- return None
169
-
170
- min_timeleft = None
171
- for task in self.running:
172
- timeleft = self.timeout - task.elapsed()
173
- if timeleft <= 0:
174
- task.timedout()
175
- continue
176
- if min_timeleft is None or timeleft < min_timeleft:
177
- min_timeleft = timeleft
178
-
179
- if min_timeleft is None:
180
- return 0
181
- else:
182
- return max(0, min_timeleft)
183
-
184
- def interrupted(self):
185
- """Cleans up after a keyboard interrupt."""
186
- for task in self.running:
187
- task.interrupted()
188
- self.finished(task)
189
-
190
- for task in self.tasks:
191
- task.cancel()
192
- self.finished(task)
193
-
194
- def finished(self, task):
195
- """Marks a task as complete and reports its status to stdout."""
196
- self.done.append(task)
197
- n = len(self.done)
198
- task.report(n)
199
-
200
-
201
- class IOMap(object):
202
- """A manager for file descriptors and their associated handlers.
203
-
204
- The poll method dispatches events to the appropriate handlers.
205
- """
206
- def __init__(self):
207
- self.readmap = {}
208
- self.writemap = {}
209
-
210
- # Setup the wakeup file descriptor to avoid hanging on lost signals.
211
- wakeup_readfd, wakeup_writefd = os.pipe()
212
- self.register_read(wakeup_readfd, self.wakeup_handler)
213
- # TODO: remove test when we stop supporting Python <2.5
214
- if hasattr(signal, 'set_wakeup_fd'):
215
- signal.set_wakeup_fd(wakeup_writefd)
216
- self.wakeup_writefd = None
217
- else:
218
- self.wakeup_writefd = wakeup_writefd
219
-
220
- def register_read(self, fd, handler):
221
- """Registers an IO handler for a file descriptor for reading."""
222
- self.readmap[fd] = handler
223
-
224
- def register_write(self, fd, handler):
225
- """Registers an IO handler for a file descriptor for writing."""
226
- self.writemap[fd] = handler
227
-
228
- def unregister(self, fd):
229
- """Unregisters the given file descriptor."""
230
- if fd in self.readmap:
231
- del self.readmap[fd]
232
- if fd in self.writemap:
233
- del self.writemap[fd]
234
-
235
- def poll(self, timeout=None):
236
- """Performs a poll and dispatches the resulting events."""
237
- if not self.readmap and not self.writemap:
238
- return
239
- rlist = list(self.readmap)
240
- wlist = list(self.writemap)
241
- try:
242
- rlist, wlist, _ = select.select(rlist, wlist, [], timeout)
243
- except select.error:
244
- _, e, _ = sys.exc_info()
245
- errno = e.args[0]
246
- if errno == EINTR:
247
- return
248
- else:
249
- raise
250
- for fd in rlist:
251
- handler = self.readmap[fd]
252
- handler(fd, self)
253
- for fd in wlist:
254
- handler = self.writemap[fd]
255
- handler(fd, self)
256
-
257
- def wakeup_handler(self, fd, iomap):
258
- """Handles read events on the signal wakeup pipe.
259
-
260
- This ensures that SIGCHLD signals aren't lost.
261
- """
262
- try:
263
- os.read(fd, READ_SIZE)
264
- except (OSError, IOError):
265
- _, e, _ = sys.exc_info()
266
- errno, message = e.args
267
- if errno != EINTR:
268
- sys.stderr.write('Fatal error reading from wakeup pipe: %s\n'
269
- % message)
270
- raise FatalError
271
-
272
-
273
- class Writer(threading.Thread):
274
- """Thread that writes to files by processing requests from a Queue.
275
-
276
- Until AIO becomes widely available, it is impossible to make a nonblocking
277
- write to an ordinary file. The Writer thread processes all writing to
278
- ordinary files so that the main thread can work without blocking.
279
- """
280
- OPEN = object()
281
- EOF = object()
282
- ABORT = object()
283
-
284
- def __init__(self, outdir, errdir):
285
- threading.Thread.__init__(self)
286
- # A daemon thread automatically dies if the program is terminated.
287
- self.setDaemon(True)
288
- self.queue = queue.Queue()
289
- self.outdir = outdir
290
- self.errdir = errdir
291
-
292
- self.host_counts = {}
293
- self.files = {}
294
-
295
- def run(self):
296
- while True:
297
- filename, data = self.queue.get()
298
- if filename == self.ABORT:
299
- return
300
-
301
- if data == self.OPEN:
302
- self.files[filename] = open(filename, 'wb', buffering=1)
303
- psshutil.set_cloexec(self.files[filename])
304
- else:
305
- dest = self.files[filename]
306
- if data == self.EOF:
307
- dest.close()
308
- else:
309
- dest.write(data)
310
-
311
- def open_files(self, host):
312
- """Called from another thread to create files for stdout and stderr.
313
-
314
- Returns a pair of filenames (outfile, errfile). These filenames are
315
- used as handles for future operations. Either or both may be None if
316
- outdir or errdir or not set.
317
- """
318
- outfile = errfile = None
319
- if self.outdir or self.errdir:
320
- count = self.host_counts.get(host, 0)
321
- self.host_counts[host] = count + 1
322
- if count:
323
- filename = "%s.%s" % (host, count)
324
- else:
325
- filename = host
326
- if self.outdir:
327
- outfile = os.path.join(self.outdir, filename)
328
- self.queue.put((outfile, self.OPEN))
329
- if self.errdir:
330
- errfile = os.path.join(self.errdir, filename)
331
- self.queue.put((errfile, self.OPEN))
332
- return outfile, errfile
333
-
334
- def write(self, filename, data):
335
- """Called from another thread to enqueue a write."""
336
- self.queue.put((filename, data))
337
-
338
- def close(self, filename):
339
- """Called from another thread to close the given file."""
340
- self.queue.put((filename, self.EOF))
341
-
342
- def signal_quit(self):
343
- """Called from another thread to request the Writer to quit."""
344
- self.queue.put((self.ABORT, None))
345
-