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.
- checksums.yaml +4 -4
- data/lib/pec2/cli.rb +1 -9
- data/lib/pec2/pssh.rb +58 -28
- data/lib/pec2/version.rb +1 -1
- data/pec2.gemspec +4 -1
- data/spec/cli_spec.rb +0 -1
- data/spec/pssh_spec.rb +1 -54
- metadata +46 -24
- data/exe/bin/pnuke +0 -96
- data/exe/bin/prsync +0 -126
- data/exe/bin/pscp +0 -108
- data/exe/bin/pslurp +0 -129
- data/exe/bin/pssh +0 -118
- data/exe/bin/pssh-askpass +0 -11
- data/exe/man/man1/pnuke.1 +0 -268
- data/exe/man/man1/prsync.1 +0 -299
- data/exe/man/man1/pscp.1 +0 -271
- data/exe/man/man1/pslurp.1 +0 -280
- data/exe/man/man1/pssh.1 +0 -368
- data/exe/psshlib/__init__.py +0 -0
- data/exe/psshlib/askpass_client.py +0 -102
- data/exe/psshlib/askpass_server.py +0 -101
- data/exe/psshlib/cli.py +0 -110
- data/exe/psshlib/color.py +0 -39
- data/exe/psshlib/manager.py +0 -345
- data/exe/psshlib/psshutil.py +0 -108
- data/exe/psshlib/task.py +0 -288
- data/exe/psshlib/version.py +0 -1
data/exe/psshlib/psshutil.py
DELETED
@@ -1,108 +0,0 @@
|
|
1
|
-
# Copyright (c) 2009-2012, Andrew McNabb
|
2
|
-
# Copyright (c) 2003-2008, Brent N. Chun
|
3
|
-
|
4
|
-
import fcntl
|
5
|
-
import string
|
6
|
-
import sys
|
7
|
-
|
8
|
-
HOST_FORMAT = 'Host format is [user@]host[:port] [user]'
|
9
|
-
|
10
|
-
|
11
|
-
def read_host_files(paths, default_user=None, default_port=None):
|
12
|
-
"""Reads the given host files.
|
13
|
-
|
14
|
-
Returns a list of (host, port, user) triples.
|
15
|
-
"""
|
16
|
-
hosts = []
|
17
|
-
if paths:
|
18
|
-
for path in paths:
|
19
|
-
hosts.extend(read_host_file(path, default_user=default_user))
|
20
|
-
return hosts
|
21
|
-
|
22
|
-
|
23
|
-
def read_host_file(path, default_user=None, default_port=None):
|
24
|
-
"""Reads the given host file.
|
25
|
-
|
26
|
-
Lines are of the form: host[:port] [login].
|
27
|
-
Returns a list of (host, port, user) triples.
|
28
|
-
"""
|
29
|
-
lines = []
|
30
|
-
f = open(path)
|
31
|
-
for line in f:
|
32
|
-
lines.append(line.strip())
|
33
|
-
f.close()
|
34
|
-
|
35
|
-
hosts = []
|
36
|
-
for line in lines:
|
37
|
-
# Skip blank lines or lines starting with #
|
38
|
-
line = line.strip()
|
39
|
-
if not line or line.startswith('#'):
|
40
|
-
continue
|
41
|
-
host, port, user = parse_host_entry(line, default_user, default_port)
|
42
|
-
if host:
|
43
|
-
hosts.append((host, port, user))
|
44
|
-
return hosts
|
45
|
-
|
46
|
-
|
47
|
-
# TODO: deprecate the second host field and standardize on the
|
48
|
-
# [user@]host[:port] format.
|
49
|
-
def parse_host_entry(line, default_user, default_port):
|
50
|
-
"""Parses a single host entry.
|
51
|
-
|
52
|
-
This may take either the of the form [user@]host[:port] or
|
53
|
-
host[:port][ user].
|
54
|
-
|
55
|
-
Returns a (host, port, user) triple.
|
56
|
-
"""
|
57
|
-
fields = line.split()
|
58
|
-
if len(fields) > 2:
|
59
|
-
sys.stderr.write('Bad line: "%s". Format should be'
|
60
|
-
' [user@]host[:port] [user]\n' % line)
|
61
|
-
return None, None, None
|
62
|
-
host_field = fields[0]
|
63
|
-
host, port, user = parse_host(host_field, default_port=default_port)
|
64
|
-
if len(fields) == 2:
|
65
|
-
if user is None:
|
66
|
-
user = fields[1]
|
67
|
-
else:
|
68
|
-
sys.stderr.write('User specified twice in line: "%s"\n' % line)
|
69
|
-
return None, None, None
|
70
|
-
if user is None:
|
71
|
-
user = default_user
|
72
|
-
return host, port, user
|
73
|
-
|
74
|
-
|
75
|
-
def parse_host_string(host_string, default_user=None, default_port=None):
|
76
|
-
"""Parses a whitespace-delimited string of "[user@]host[:port]" entries.
|
77
|
-
|
78
|
-
Returns a list of (host, port, user) triples.
|
79
|
-
"""
|
80
|
-
hosts = []
|
81
|
-
entries = host_string.split()
|
82
|
-
for entry in entries:
|
83
|
-
hosts.append(parse_host(entry, default_user, default_port))
|
84
|
-
return hosts
|
85
|
-
|
86
|
-
|
87
|
-
def parse_host(host, default_user=None, default_port=None):
|
88
|
-
"""Parses host entries of the form "[user@]host[:port]".
|
89
|
-
|
90
|
-
Returns a (host, port, user) triple.
|
91
|
-
"""
|
92
|
-
# TODO: when we stop supporting Python 2.4, switch to using str.partition.
|
93
|
-
user = default_user
|
94
|
-
port = default_port
|
95
|
-
if '@' in host:
|
96
|
-
user, host = host.split('@', 1)
|
97
|
-
if ':' in host:
|
98
|
-
host, port = host.rsplit(':', 1)
|
99
|
-
return (host, port, user)
|
100
|
-
|
101
|
-
|
102
|
-
def set_cloexec(filelike):
|
103
|
-
"""Sets the underlying filedescriptor to automatically close on exec.
|
104
|
-
|
105
|
-
If set_cloexec is called for all open files, then subprocess.Popen does
|
106
|
-
not require the close_fds option.
|
107
|
-
"""
|
108
|
-
fcntl.fcntl(filelike.fileno(), fcntl.FD_CLOEXEC, 1)
|
data/exe/psshlib/task.py
DELETED
@@ -1,288 +0,0 @@
|
|
1
|
-
# Copyright (c) 2009-2012, Andrew McNabb
|
2
|
-
|
3
|
-
from errno import EINTR
|
4
|
-
from subprocess import Popen, PIPE
|
5
|
-
import os
|
6
|
-
import signal
|
7
|
-
import sys
|
8
|
-
import time
|
9
|
-
import traceback
|
10
|
-
|
11
|
-
from psshlib import askpass_client
|
12
|
-
from psshlib import color
|
13
|
-
|
14
|
-
BUFFER_SIZE = 1 << 16
|
15
|
-
|
16
|
-
try:
|
17
|
-
bytes
|
18
|
-
except NameError:
|
19
|
-
bytes = str
|
20
|
-
|
21
|
-
|
22
|
-
class Task(object):
|
23
|
-
"""Starts a process and manages its input and output.
|
24
|
-
|
25
|
-
Upon completion, the `exitstatus` attribute is set to the exit status
|
26
|
-
of the process.
|
27
|
-
"""
|
28
|
-
def __init__(self, host, port, user, cmd, opts, stdin=None):
|
29
|
-
self.exitstatus = None
|
30
|
-
|
31
|
-
self.host = host
|
32
|
-
self.pretty_host = host
|
33
|
-
self.port = port
|
34
|
-
self.cmd = cmd
|
35
|
-
|
36
|
-
if user != opts.user:
|
37
|
-
self.pretty_host = '@'.join((user, self.pretty_host))
|
38
|
-
if port:
|
39
|
-
self.pretty_host = ':'.join((self.pretty_host, port))
|
40
|
-
|
41
|
-
self.proc = None
|
42
|
-
self.writer = None
|
43
|
-
self.timestamp = None
|
44
|
-
self.failures = []
|
45
|
-
self.killed = False
|
46
|
-
self.inputbuffer = stdin
|
47
|
-
self.byteswritten = 0
|
48
|
-
self.outputbuffer = bytes()
|
49
|
-
self.errorbuffer = bytes()
|
50
|
-
|
51
|
-
self.stdin = None
|
52
|
-
self.stdout = None
|
53
|
-
self.stderr = None
|
54
|
-
self.outfile = None
|
55
|
-
self.errfile = None
|
56
|
-
|
57
|
-
# Set options.
|
58
|
-
self.verbose = opts.verbose
|
59
|
-
try:
|
60
|
-
self.print_out = bool(opts.print_out)
|
61
|
-
except AttributeError:
|
62
|
-
self.print_out = False
|
63
|
-
try:
|
64
|
-
self.inline = bool(opts.inline)
|
65
|
-
except AttributeError:
|
66
|
-
self.inline = False
|
67
|
-
try:
|
68
|
-
self.inline_stdout = bool(opts.inline_stdout)
|
69
|
-
except AttributeError:
|
70
|
-
self.inline_stdout = False
|
71
|
-
|
72
|
-
def start(self, nodenum, iomap, writer, askpass_socket=None):
|
73
|
-
"""Starts the process and registers files with the IOMap."""
|
74
|
-
self.writer = writer
|
75
|
-
|
76
|
-
if writer:
|
77
|
-
self.outfile, self.errfile = writer.open_files(self.pretty_host)
|
78
|
-
|
79
|
-
# Set up the environment.
|
80
|
-
environ = dict(os.environ)
|
81
|
-
environ['PSSH_NODENUM'] = str(nodenum)
|
82
|
-
environ['PSSH_HOST'] = self.host
|
83
|
-
# Disable the GNOME pop-up password dialog and allow ssh to use
|
84
|
-
# askpass.py to get a provided password. If the module file is
|
85
|
-
# askpass.pyc, we replace the extension.
|
86
|
-
environ['SSH_ASKPASS'] = askpass_client.executable_path()
|
87
|
-
if askpass_socket:
|
88
|
-
environ['PSSH_ASKPASS_SOCKET'] = askpass_socket
|
89
|
-
if self.verbose:
|
90
|
-
environ['PSSH_ASKPASS_VERBOSE'] = '1'
|
91
|
-
# Work around a mis-feature in ssh where it won't call SSH_ASKPASS
|
92
|
-
# if DISPLAY is unset.
|
93
|
-
if 'DISPLAY' not in environ:
|
94
|
-
environ['DISPLAY'] = 'pssh-gibberish'
|
95
|
-
|
96
|
-
# Create the subprocess. Since we carefully call set_cloexec() on
|
97
|
-
# all open files, we specify close_fds=False.
|
98
|
-
self.proc = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
99
|
-
close_fds=False, preexec_fn=os.setsid, env=environ)
|
100
|
-
self.timestamp = time.time()
|
101
|
-
if self.inputbuffer:
|
102
|
-
self.stdin = self.proc.stdin
|
103
|
-
iomap.register_write(self.stdin.fileno(), self.handle_stdin)
|
104
|
-
else:
|
105
|
-
self.proc.stdin.close()
|
106
|
-
self.stdout = self.proc.stdout
|
107
|
-
iomap.register_read(self.stdout.fileno(), self.handle_stdout)
|
108
|
-
self.stderr = self.proc.stderr
|
109
|
-
iomap.register_read(self.stderr.fileno(), self.handle_stderr)
|
110
|
-
|
111
|
-
def _kill(self):
|
112
|
-
"""Signals the process to terminate."""
|
113
|
-
if self.proc:
|
114
|
-
try:
|
115
|
-
os.kill(-self.proc.pid, signal.SIGKILL)
|
116
|
-
except OSError:
|
117
|
-
# If the kill fails, then just assume the process is dead.
|
118
|
-
pass
|
119
|
-
self.killed = True
|
120
|
-
|
121
|
-
def timedout(self):
|
122
|
-
"""Kills the process and registers a timeout error."""
|
123
|
-
if not self.killed:
|
124
|
-
self._kill()
|
125
|
-
self.failures.append('Timed out')
|
126
|
-
|
127
|
-
def interrupted(self):
|
128
|
-
"""Kills the process and registers an keyboard interrupt error."""
|
129
|
-
if not self.killed:
|
130
|
-
self._kill()
|
131
|
-
self.failures.append('Interrupted')
|
132
|
-
|
133
|
-
def cancel(self):
|
134
|
-
"""Stops a task that has not started."""
|
135
|
-
self.failures.append('Cancelled')
|
136
|
-
|
137
|
-
def elapsed(self):
|
138
|
-
"""Finds the time in seconds since the process was started."""
|
139
|
-
return time.time() - self.timestamp
|
140
|
-
|
141
|
-
def running(self):
|
142
|
-
"""Finds if the process has terminated and saves the return code."""
|
143
|
-
if self.stdin or self.stdout or self.stderr:
|
144
|
-
return True
|
145
|
-
if self.proc:
|
146
|
-
self.exitstatus = self.proc.poll()
|
147
|
-
if self.exitstatus is None:
|
148
|
-
if self.killed:
|
149
|
-
# Set the exitstatus to what it would be if we waited.
|
150
|
-
self.exitstatus = -signal.SIGKILL
|
151
|
-
return False
|
152
|
-
else:
|
153
|
-
return True
|
154
|
-
else:
|
155
|
-
if self.exitstatus < 0:
|
156
|
-
message = 'Killed by signal %s' % (-self.exitstatus)
|
157
|
-
self.failures.append(message)
|
158
|
-
elif self.exitstatus > 0:
|
159
|
-
message = 'Exited with error code %s' % self.exitstatus
|
160
|
-
self.failures.append(message)
|
161
|
-
self.proc = None
|
162
|
-
return False
|
163
|
-
|
164
|
-
def handle_stdin(self, fd, iomap):
|
165
|
-
"""Called when the process's standard input is ready for writing."""
|
166
|
-
try:
|
167
|
-
start = self.byteswritten
|
168
|
-
if start < len(self.inputbuffer):
|
169
|
-
chunk = self.inputbuffer[start:start+BUFFER_SIZE]
|
170
|
-
self.byteswritten = start + os.write(fd, chunk)
|
171
|
-
else:
|
172
|
-
self.close_stdin(iomap)
|
173
|
-
except (OSError, IOError):
|
174
|
-
_, e, _ = sys.exc_info()
|
175
|
-
if e.errno != EINTR:
|
176
|
-
self.close_stdin(iomap)
|
177
|
-
self.log_exception(e)
|
178
|
-
|
179
|
-
def close_stdin(self, iomap):
|
180
|
-
if self.stdin:
|
181
|
-
iomap.unregister(self.stdin.fileno())
|
182
|
-
self.stdin.close()
|
183
|
-
self.stdin = None
|
184
|
-
|
185
|
-
def handle_stdout(self, fd, iomap):
|
186
|
-
"""Called when the process's standard output is ready for reading."""
|
187
|
-
try:
|
188
|
-
buf = os.read(fd, BUFFER_SIZE)
|
189
|
-
if buf:
|
190
|
-
if self.inline or self.inline_stdout:
|
191
|
-
self.outputbuffer += buf
|
192
|
-
if self.outfile:
|
193
|
-
self.writer.write(self.outfile, buf)
|
194
|
-
if self.print_out:
|
195
|
-
sys.stdout.write('%s: %s' % (self.host, buf))
|
196
|
-
if buf[-1] != '\n':
|
197
|
-
sys.stdout.write('\n')
|
198
|
-
else:
|
199
|
-
self.close_stdout(iomap)
|
200
|
-
except (OSError, IOError):
|
201
|
-
_, e, _ = sys.exc_info()
|
202
|
-
if e.errno != EINTR:
|
203
|
-
self.close_stdout(iomap)
|
204
|
-
self.log_exception(e)
|
205
|
-
|
206
|
-
def close_stdout(self, iomap):
|
207
|
-
if self.stdout:
|
208
|
-
iomap.unregister(self.stdout.fileno())
|
209
|
-
self.stdout.close()
|
210
|
-
self.stdout = None
|
211
|
-
if self.outfile:
|
212
|
-
self.writer.close(self.outfile)
|
213
|
-
self.outfile = None
|
214
|
-
|
215
|
-
def handle_stderr(self, fd, iomap):
|
216
|
-
"""Called when the process's standard error is ready for reading."""
|
217
|
-
try:
|
218
|
-
buf = os.read(fd, BUFFER_SIZE)
|
219
|
-
if buf:
|
220
|
-
if self.inline:
|
221
|
-
self.errorbuffer += buf
|
222
|
-
if self.errfile:
|
223
|
-
self.writer.write(self.errfile, buf)
|
224
|
-
else:
|
225
|
-
self.close_stderr(iomap)
|
226
|
-
except (OSError, IOError):
|
227
|
-
_, e, _ = sys.exc_info()
|
228
|
-
if e.errno != EINTR:
|
229
|
-
self.close_stderr(iomap)
|
230
|
-
self.log_exception(e)
|
231
|
-
|
232
|
-
def close_stderr(self, iomap):
|
233
|
-
if self.stderr:
|
234
|
-
iomap.unregister(self.stderr.fileno())
|
235
|
-
self.stderr.close()
|
236
|
-
self.stderr = None
|
237
|
-
if self.errfile:
|
238
|
-
self.writer.close(self.errfile)
|
239
|
-
self.errfile = None
|
240
|
-
|
241
|
-
def log_exception(self, e):
|
242
|
-
"""Saves a record of the most recent exception for error reporting."""
|
243
|
-
if self.verbose:
|
244
|
-
exc_type, exc_value, exc_traceback = sys.exc_info()
|
245
|
-
exc = ("Exception: %s, %s, %s" %
|
246
|
-
(exc_type, exc_value, traceback.format_tb(exc_traceback)))
|
247
|
-
else:
|
248
|
-
exc = str(e)
|
249
|
-
self.failures.append(exc)
|
250
|
-
|
251
|
-
def report(self, n):
|
252
|
-
"""Pretty prints a status report after the Task completes."""
|
253
|
-
error = ', '.join(self.failures)
|
254
|
-
tstamp = time.asctime().split()[3] # Current time
|
255
|
-
if color.has_colors(sys.stdout):
|
256
|
-
progress = color.c("[%s]" % color.B(n))
|
257
|
-
success = color.g("[%s]" % color.B("SUCCESS"))
|
258
|
-
failure = color.r("[%s]" % color.B("FAILURE"))
|
259
|
-
stderr = color.r("Stderr: ")
|
260
|
-
error = color.r(color.B(error))
|
261
|
-
else:
|
262
|
-
progress = "[%s]" % n
|
263
|
-
success = "[SUCCESS]"
|
264
|
-
failure = "[FAILURE]"
|
265
|
-
stderr = "Stderr: "
|
266
|
-
host = self.pretty_host
|
267
|
-
if self.failures:
|
268
|
-
print(' '.join((progress, tstamp, failure, host, error)))
|
269
|
-
else:
|
270
|
-
print(' '.join((progress, tstamp, success, host)))
|
271
|
-
# NOTE: The extra flushes are to ensure that the data is output in
|
272
|
-
# the correct order with the C implementation of io.
|
273
|
-
if self.outputbuffer:
|
274
|
-
sys.stdout.flush()
|
275
|
-
try:
|
276
|
-
sys.stdout.buffer.write(self.outputbuffer)
|
277
|
-
sys.stdout.flush()
|
278
|
-
except AttributeError:
|
279
|
-
sys.stdout.write(self.outputbuffer)
|
280
|
-
if self.errorbuffer:
|
281
|
-
sys.stdout.write(stderr)
|
282
|
-
# Flush the TextIOWrapper before writing to the binary buffer.
|
283
|
-
sys.stdout.flush()
|
284
|
-
try:
|
285
|
-
sys.stdout.buffer.write(self.errorbuffer)
|
286
|
-
except AttributeError:
|
287
|
-
sys.stdout.write(self.errorbuffer)
|
288
|
-
|
data/exe/psshlib/version.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
VERSION = '2.3.1'
|