pssh-2.2.2/0000755002342500234200000000000011522363020012643 5ustar amcnabbaml00000000000000pssh-2.2.2/PKG-INFO0000644002342500234200000000251111522363020013737 0ustar amcnabbaml00000000000000Metadata-Version: 1.0 Name: pssh Version: 2.2.2 Summary: Parallel version of OpenSSH and related tools Home-page: http://code.google.com/p/parallel-ssh/ Author: Andrew McNabb Author-email: amcnabb@mcnabbs.org License: BSD Description: PSSH (Parallel SSH) provides parallel versions of OpenSSH and related tools, including pssh, pscp, prsync, pnuke, and pslurp. The project includes psshlib which can be used within custom applications. Platform: linux Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.0 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Clustering Classifier: Topic :: System :: Networking Classifier: Topic :: System :: Systems Administration pssh-2.2.2/setup.py0000644002342500234200000000325711522362606014375 0ustar amcnabbaml00000000000000from distutils.core import setup import os long_description = """PSSH (Parallel SSH) provides parallel versions of OpenSSH and related tools, including pssh, pscp, prsync, pnuke, and pslurp. The project includes psshlib which can be used within custom applications.""" setup( name = "pssh", version = "2.2.2", author = "Andrew McNabb", author_email = "amcnabb@mcnabbs.org", url = "http://code.google.com/p/parallel-ssh/", description = "Parallel version of OpenSSH and related tools", long_description = long_description, license = "BSD", platforms = ['linux'], classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Clustering", "Topic :: System :: Networking", "Topic :: System :: Systems Administration", ], packages=['psshlib'], scripts = [os.path.join("bin", p) for p in ["pssh", "pnuke", "prsync", "pslurp", "pscp", "pssh-askpass"]], data_files=[('man/man1', ['man/man1/pssh.1'])], ) pssh-2.2.2/ChangeLog0000644002342500234200000002175111522362556014440 0ustar amcnabbaml000000000000002011-02-02 Andrew McNabb * Version 2.2.2 - Fixed two crashes (issues 35 and 36). One affects Python <= 2.5 and the other affects all of the scripts except pssh. 2011-01-26 Andrew McNabb * Version 2.2.1 - Fixed a crash when the -l option was used in conjunction with a hosts file (issue #34). 2011-01-21 Andrew McNabb * Version 2.2 - Added a basic man page for pssh (issue #10). - Fixed askpass to work correctly in the presence of non-password prompts from ssh (issue #23). - Updated the -O option so that it can be specified multiple times (issue #25). Thanks to soham.mehta for the patch. - Fixed host file loader to give an error instead of a backtrace if a file is not found. - Fixed prsync's "-ssh-args" mangling of its argument (issue #24). Thanks to jbyers for the patch. - Fixed some variable names to appease pylint. Thanks to solj for the patch. - Improved pscp to be able to copy multiple local files. Thanks to Carlo Marcelo Arenas Belon for the patch. - Deprecated the PSSH_HOSTS environment variable that seems to cause more problems than it's worth. - Added the ability to set multiple hosts with a single -H flag (issue #26). Thanks to ilya@sukhanov.net for the patch. - Stopped passing "-q" to ssh by default (this masked error messages and reduced usability). - Removed automatic reading from stdin (deprecated in version 2.1). Please use the "-I" option instead. - Added meaningful exit status codes (issue #30). - Other minor fixes 2010-02-26 Andrew McNabb * Version 2.1.1 - Fixed a problem causing PSSH to crash with Python 2.4. 2010-02-24 Andrew McNabb * Version 2.1 - Added support for Python 3.0 and 3.1. Although PSSH has only been lightly tested with Python 3, anything that doesn't work in Python 3 is officially a bug. - Added a "-H" option for specifying hosts one-by-one instead of or in addition to a hosts file. - Added "-x" and "-X" options for passing extra command-line arguments to ssh and rsync. Also added a "-S" option to prsync for the special case of passing extra arguments to ssh (issue #2). - Added a "-I" option for specifying that pssh should read from standard input, and added a deprecation warning when standard input is used without this option (issue #12). - Made the command argument optional when the "-I" option is given, so a script can be passed to pssh on standard input (issue #5). - If a username or port is given, these are now included in the output filename, which allows different connections to be distinguished from each other (issue #7). - Added the pssh-askpass wrapper as a standalone script because setup.py was removing the executable bit from askpass.py. This fixes a "permission denied" error when using the -A option (issue #8). - Fixed a problem where pssh was unnecessarily specifying a username (issue #14). - Fixed a delay due to a lost SIGCHLD signal. - Removed extra spaces between output chunks in outdir files (issue #6). Thanks to knutsen for the fix. - Fixed a bug where pscp passed the wrong option for sending scp a custom port. Thanks to Jan Rafaj for the patch. - Fixed prsync to send the port as an option to ssh (issue #1). Thanks to Ryan Brothers for the fix. 2009-10-20 Andrew McNabb * Version 2.0 - Rewrote communication code to be more efficient. PSSH now operates with only one or two threads. - Added the ability to interrupt PSSH (with CTRL-c). - Added an option to prompt for a password. - Refactored code into a distinct library (psshlib). 2008-10-12 Brent N. Chun * Version 1.4.3 - Fixed bug in select_wrap. If timeout is None (e.g., the default for prsync, etc.), then never time out. Bug reported by Carlo Marcelo Arenas Belon (carenas at sajinet.com.pe). - Catch getopt exceptions and print usage as well as getopt exception string. Contribution from Carlo Marcelo Arenas Belon (carenas at sajinet.com.pe). - Added contribution from Bas van der Vlies (basv at sara.nl) to allow comments in hosts file. Comments must begin with # character (leading whitespace is also allowed). - Restore file status bugs after reading stdin in pssh. - Fixed typo bug in pslurp when using options and in non-recursive mode. - Removed conflicts with built-in names. 2008-09-01 Brent N. Chun * Version 1.4.2 - Fixed minor bug: select returns select.error on an error, not OSError. 2008-08-27 Brent N. Chun * Version 1.4.1 - Removed broken SIGCHLD handler. - Refining subprocess _cleanup dynamically to an empty lambda function since subprocess is not thread-safe and we already call wait on child processes ourselves anyway. - Adding missing verbose flag to rest of bin/* programs. 2008-08-24 Brent N. Chun * Version 1.4.0 - Fixed 64-bit bug in pslurp, pscp, prsync. Previously, the default select timeout was sys.maxint, but this is a 64-bit value on 64-machines. Now using None when calling select when there is no timeout. Bug reported by (buixor at gmail.com). - Catching EINTR and ignoring it for select, read, write in BaseThread class. - Fixed longopts for pnuke, prsync, pscp, pslurp, pssh (bug reported a Debian user via Andrew Pollock (apollock at debian.org)). Reference: "bug #481901: pssh: options mis-specified" - Added missing environment variables for options for pnuke, prsync, pscp, pslurp, pssh. 2008-06-04 Brent N. Chun * Version 1.3.2 - Added shortopts bug fix from Lev Givon (lev at columbia.edu) in bin/pssh. 2007-04-11 Brent N. Chun * Version 1.3.1 - Reverted I/O back to 1.2.2. style pssh I/O. 2007-04-10 Brent N. Chun * Version 1.3.0 - Added contributions from Deepak Giridharagopal (deepak at brownman.org) * Added ANSI color to pssh, pscp, etc. output. * Each status message now includes a timestamp. * Failures now indicate the cause (e.g., timeout, etc.) * Intermediate directories are created as needed for output. * Removed use of setsid in shell exec. * Using Exception objects rather than raw strings. * Added support for piping stdin to each ssh process. * Added -i option to pssh for "inlining" output to stdout. * Added Python setup.py file for a standard install. - Switched to BSD license. 2006-06-18 Brent N. Chun * Version 1.2.2 - Added patch from Dan Silverstein (dans at pch.net) to fix parsecmdline bug. 2005-12-31 Brent N. Chun * Version 1.2.1 - Changed sys.path so pssh can run without RPM install. - Changed RPM library files to install in /usr/local/lib/python - make install and make uninstall now work as expected for installations from source. 2004-11-10 Brent N. Chun * Added patch from Dave Barr - Adds -a, -z flags to prsync 2004-10-05 Brent N. Chun * Default user is now current user in all programs (on suggestion from Jim Wight ). * Fixed path typo on prsync from 1.1.0 release * Version 1.1.1 2004-10-04 Brent N. Chun * Added patch from Dave Barr - Adds an ssh options flag (-O) to prsync * Added patch from Chad Yoshikawa - Adds a print to stdout flag (-P) to pssh * Version 1.1.0 2004-08-21 Brent N. Chun * All cmds now take -o, -e for stdout, stderr * Checking return values for all cmds * Factored common thread structure out of all cmds * Changed pslurp's dir for local to -L, rather than -o (stdout) * Version 1.0.0 2003-11-20 Brent N. Chun * Added handler for SIGCHLD * Wait for all threads before returning to main thread * Kill all straggler processes when done 2003-11-18 Brent N. Chun * Added pslurp (scp from remote nodes), updated to 0.2.3 2003-11-12 Brent N. Chun * Fixed read bug, so all output is obtained. * Added timeout option (defaults to None for pscp/prsync) * Added verbose option for pssh/pnuke (this is -q or not) * Added environment variables for options * Fixed usage for pnuke 2003-09-06 Brent N. Chun * Added -O for pssh, pscp, and pnuke for passing SSH options * Changed order of options in usage (required, optional) 2003-09-06 Brent N. Chun * Added parallel rsync (prsync) * Added support for "host[:port] user" lines in hosts files * Factored a bit of code out into lib/python/psshutil.py 2003-08-16 Brent N. Chun * Initial version (0.1.0) pssh-2.2.2/INSTALL0000644002342500234200000000124111341274611013700 0ustar amcnabbaml00000000000000Installing PSSH =============== If you don't already have setuptools installed: # wget 'http://peak.telecommunity.com/dist/ez_setup.py' # sudo python ez_setup.py Then: # sudo python setup.py install Share and enjoy! Instructions for Packagers -------------------------- Packagers create RPM or deb files for Linux distributions. If you are a normal user, please skip this section. Packaging PSSH is pretty straightforward. Just do a setup.py install, and then move usr/bin/pssh-askpass to usr/libexec/pssh/pssh-askpass. Although having pssh-askpass in /usr/bin doesn't really hurt anything, it's not necessary, so it's cleaner to move it to /usr/libexec. pssh-2.2.2/COPYING0000644002342500234200000000276711270130553013715 0ustar amcnabbaml00000000000000Copyright (c) 2009, Andrew McNabb Copyright (c) 2003-2008, Brent N. Chun All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pssh-2.2.2/man/0000755002342500234200000000000011522363020013416 5ustar amcnabbaml00000000000000pssh-2.2.2/man/man1/0000755002342500234200000000000011522363020014252 5ustar amcnabbaml00000000000000pssh-2.2.2/man/man1/pssh.10000644002342500234200000001423511516402337015326 0ustar amcnabbaml00000000000000.\" Man page for pssh. See "man 7 man" and "man man-pages" for formatting info. .TH pssh 1 "February 25, 2010" .SH NAME pssh \(em parallel ssh program .SH SYNOPSIS .B pssh .RB [ \-vAiIP ] .RB [ \-h .IR hosts_file ] .RB [ \-H .RI [ user @] host [: port ]] .RB [ \-l .IR user ] .RB [ \-p .IR par ] .RB [ \-o .IR outdir ] .RB [ \-e .IR errdir ] .RB [ \-t .IR timeout ] .RB [ \-O .IR options ] .RB [ \-x .IR args ] .RB [ \-X .IR arg ] .I command ... .B pssh \-I .RB [ \-vAiIP ] .RB [ \-h .IR hosts_file ] .RB [ \-H .RI [ user @] host [: port ]] .RB [ \-l .IR user ] .RB [ \-p .IR par ] .RB [ \-o .IR outdir ] .RB [ \-e .IR errdir ] .RB [ \-t .IR timeout ] .RB [ \-O .IR options ] .RB [ \-x .IR args ] .RB [ \-X .IR arg ] .RI [ command .IR ... ] .SH DESCRIPTION .PP .B pssh is a program for executing ssh in parallel on a number of hosts. It provides features such as sending input to all of the processes, passing a password to ssh, saving output to files, and timing out. .SH OPTIONS .TP .BI \-h " host_file" .PD 0 .TP .BI \-\-hosts " host_file" Read hosts from the given .IR host_file . Lines in the host file are of the form .RI [ user @] host [: port ] and can include blank lines and comments (lines beginning with "#"). If multiple host files are given (the .B \-h option is used more than once), then pssh behaves as though these files were concatenated together. If a host is specified specified multiple times, then pssh will connect the given number of times. .TP .B \-H .RI [ user @] host [: port ] .PD 0 .TP .B \-\-host .RI [ user @] host [: port ] .PD 0 .TP .B \-H .RI \(dq[ user @] host [: port ] [ .RI [ user @] host [: port ] ... ]\(dq .PD 0 .TP .B \-\-host .RI \(dq[ user @] host [: port ] [ .RI [ user @] host [: port ] ... ]\(dq .PD 0 .IP Add the given host strings to the list of hosts. This option may be given multiple times, and may be used in conjunction with the .B \-h option. .TP .BI \-l " user" .PD 0 .TP .BI \-\-user " user" Use the given username as the default for any host entries that don't specifically specify a user. .TP .BI \-p " parallelism" .PD 0 .TP .BI \-\-par " parallelism" Use the given number as the maximum number of concurrent connections. .TP .BI \-t " timeout" .PD 0 .TP .BI \-\-timeout " timeout" Make connections time out after the given number of seconds. With a value of 0, pssh will not timeout any connections. .TP .BI \-o " outdir" .PD 0 .TP .BI \-\-outdir " outdir" Save standard output to files in the given directory. Filenames are of the form .RI [ user @] host [: port ][. num ] where the user and port are only included for hosts that explicitly specify them. The number is a counter that is incremented each time for hosts that are specified more than once. .TP .BI \-e " errdir" .PD 0 .TP .BI \-\-errdir " errdir" Save standard error to files in the given directory. Filenames are of the same form as with the .B \-o option. .TP .BI \-x " args" .PD 0 .TP .BI \-\-extra-args " args" Passes a extra SSH command-line arguments (see the .BR ssh (1) man page for more information about SSH arguments). This option may be specified multiple times. The arguments are processed to split on whitespace, protect text within quotes, and escape with backslashes. To pass arguments without such processing, use the .B \-X option instead. .TP .BI \-X " arg" .PD 0 .TP .BI \-\-extra-arg " arg" Passes a single SSH command-line argument (see the .BR ssh (1) man page for more information about SSH arguments). Unlike the .B \-x option, no processing is performed on the argument, including word splitting. To pass multiple command-line arguments, use the option once for each argument. .TP .BI \-O " options" .PD 0 .TP .BI \-\-options " options" SSH options in the format used in the SSH configuration file (see the .BR ssh_config (5) man page for more information). This option may be specified multiple times. .TP .B \-A .PD 0 .TP .B \-\-askpass Prompt for a password and pass it to ssh. The password may be used for either to unlock a key or for password authentication. The password is transferred in a fairly secure manner (e.g., it will not show up in argument lists). However, be aware that a root user on your system could potentially intercept the password. .TP .B \-i .PD 0 .TP .B \-\-inline Display standard output and standard error as each host completes. .TP .B \-v .PD 0 .TP .B \-\-verbose Include error messages from ssh with the .B \-i and .B \e options. .TP .B \-I .PD 0 .TP .B \-\-send-input Read input and send to each ssh process. Since ssh allows a command script to be sent on standard input, the .B \-I option may be used in lieu of the command argument. .TP .B \-P .PD 0 .TP .B \-\-print Display output as it arrives. This option is of limited usefulness because output from different hosts are interleaved. .SH EXAMPLE .PP Connect to host1 and host2, and print "hello, world" from each: .RS pssh -i -H "host1 host2" echo "hello, world" .RE .PP Print "hello, world" from each host specified in the file hosts.txt: .RS pssh -i -h hosts.txt echo "hello, world" .RE .PP Run a command as root with a prompt for the root password: .RS pssh -i -h hosts.txt -A -l root echo hi .RE .PP Run a long command without timing out: .RS pssh -i -h hosts.txt -t 0 sleep 10000 .RE .PP If the file hosts.txt has a large number of entries, say 100, then the parallelism option may also be set to 100 to ensure that the commands are run concurrently: .RS pssh -i -h hosts.txt -p 100 -t 0 sleep 10000 .RE .PP Run a command without checking or saving host keys: .RS pssh -i -H host1 -H host2 -x "-O StrictHostKeyChecking=no -O UserKnownHostsFile=/dev/null -O GlobalKnownHostsFile=/dev/null" echo hi .RE .SH EXIT STATUS VALUES .PP .TP .B 0 Success .TP .B 1 Miscellaneous error .TP .B 2 Syntax or usage error .TP .B 3 At least one process was killed by a signal or timed out. .TP .B 4 All processes completed, but at least one ssh process reported an error (exit status 255). .TP .B 5 There were no ssh errors, but at least one remote command had a non-zero exit status. .SH AUTHORS .PP Written by Brent N. Chun and Andrew McNabb . http://code.google.com/p/parallel-ssh/ .SH SEE ALSO .BR ssh (1), .BR pscp (1), .BR prsync (1), .BR pslurp (1), .BR pnuke (1) pssh-2.2.2/bin/0000755002342500234200000000000011522363020013413 5ustar amcnabbaml00000000000000pssh-2.2.2/bin/pssh-askpass0000755002342500234200000000041611341303724015766 0ustar amcnabbaml00000000000000#!/usr/bin/env python import os import sys parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) if os.path.exists(os.path.join(parent, 'psshlib')): sys.path.insert(0, parent) from psshlib.askpass_client import askpass_main askpass_main() pssh-2.2.2/bin/prsync0000755002342500234200000000746511522362206014700 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun """Parallel rsync to the set of nodes in hosts.txt. For each node, we essentially do a rsync -rv -e ssh local user@host:remote. Note that remote must be an absolute path. """ import os import re import sys parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) if os.path.exists(os.path.join(parent, 'psshlib')): sys.path.insert(0, parent) from psshlib import psshutil from psshlib.task import Task from psshlib.manager import Manager, FatalError from psshlib.cli import common_parser, common_defaults def option_parser(): parser = common_parser() parser.usage = "%prog [OPTIONS] -h hosts.txt local remote" parser.epilog = ("Example: prsync -r -h hosts.txt -l irb2 foo " + "/home/irb2/foo") parser.add_option('-r', '--recursive', dest='recursive', action='store_true', help='recusively copy directories (OPTIONAL)') parser.add_option('-a', '--archive', dest='archive', action='store_true', help='use rsync -a (archive mode) (OPTIONAL)') parser.add_option('-z', '--compress', dest='compress', action='store_true', help='use rsync compression (OPTIONAL)') parser.add_option('-S', '--ssh-args', metavar="ARGS", dest='ssh_args', action='store', help='extra arguments for ssh') return parser def parse_args(): parser = option_parser() defaults = common_defaults() parser.set_defaults(**defaults) opts, args = parser.parse_args() if len(args) < 1: parser.error('Paths not specified.') if len(args) < 2: parser.error('Remote path not specified.') if len(args) > 2: parser.error('Extra arguments given after the remote path.') if not opts.host_files and not opts.host_strings: parser.error('Hosts not specified.') return opts, args def do_prsync(hosts, local, remote, opts): if opts.outdir and not os.path.exists(opts.outdir): os.makedirs(opts.outdir) if opts.errdir and not os.path.exists(opts.errdir): os.makedirs(opts.errdir) manager = Manager(opts) for host, port, user in hosts: ssh = ['ssh'] if opts.options: ssh += ['-o', opts.options] if port: ssh += ['-p', port] if opts.ssh_args: ssh += [opts.ssh_args] cmd = ['rsync', '-e', ' '.join(ssh)] if opts.verbose: cmd.append('-v') if opts.recursive: cmd.append('-r') if opts.archive: cmd.append('-a') if opts.compress: cmd.append('-z') if opts.extra: cmd.extend(opts.extra) cmd.append(local) if user: cmd.append('%s@%s:%s' % (user, host, remote)) else: cmd.append('%s:%s' % (host, remote)) t = Task(host, port, user, cmd, opts) manager.add_task(t) try: statuses = manager.run() except FatalError: sys.exit(1) if min(statuses) < 0: # At least one process was killed. sys.exit(3) for status in statuses: if status != 0: sys.exit(4) if __name__ == "__main__": opts, args = parse_args() local = args[0] remote = args[1] if not re.match("^/", remote): print("Remote path %s must be an absolute path" % remote) sys.exit(3) try: hosts = psshutil.read_host_files(opts.host_files, default_user=opts.user) except IOError: _, e, _ = sys.exc_info() sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) sys.exit(1) if opts.host_strings: for s in opts.host_strings: hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) do_prsync(hosts, local, remote, opts) pssh-2.2.2/bin/pnuke0000755002342500234200000000522411522362206014473 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun """Nukes all processes that match pattern running as user on the set of nodes in hosts.txt. """ import os import sys parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) if os.path.exists(os.path.join(parent, 'psshlib')): sys.path.insert(0, parent) from psshlib import psshutil from psshlib.task import Task from psshlib.manager import Manager, FatalError from psshlib.cli import common_parser, common_defaults _DEFAULT_TIMEOUT = 60 def option_parser(): parser = common_parser() parser.usage = "%prog [OPTIONS] -h hosts.txt pattern" parser.epilog = "Example: pnuke -h hosts.txt -l irb2 java" return parser def parse_args(): parser = option_parser() defaults = common_defaults(timeout=_DEFAULT_TIMEOUT) parser.set_defaults(**defaults) opts, args = parser.parse_args() if len(args) < 1: parser.error('Pattern not specified.') if len(args) > 1: parser.error('Extra arguments given after the pattern.') if not opts.host_files and not opts.host_strings: parser.error('Hosts not specified.') return opts, args def do_pnuke(hosts, pattern, opts): if opts.outdir and not os.path.exists(opts.outdir): os.makedirs(opts.outdir) if opts.errdir and not os.path.exists(opts.errdir): os.makedirs(opts.errdir) manager = Manager(opts) for host, port, user in hosts: cmd = ['ssh', host, '-o', 'NumberOfPasswordPrompts=1'] if opts.options: for opt in opts.options: cmd += ['-o', opt] if user: cmd += ['-l', user] if port: cmd += ['-p', port] if opts.extra: cmd.extend(opts.extra) cmd.append('pkill -9 %s' % pattern) t = Task(host, port, user, cmd, opts) manager.add_task(t) try: statuses = manager.run() except FatalError: sys.exit(1) if min(statuses) < 0: # At least one process was killed. sys.exit(3) for status in statuses: if status != 0: sys.exit(4) if __name__ == "__main__": opts, args = parse_args() pattern = args[0] try: hosts = psshutil.read_host_files(opts.host_files, default_user=opts.user) except IOError: _, e, _ = sys.exc_info() sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) sys.exit(1) if opts.host_strings: for s in opts.host_strings: hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) do_pnuke(hosts, pattern, opts) pssh-2.2.2/bin/pssh0000755002342500234200000000713511516404176014337 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun """Parallel ssh to the set of nodes in hosts.txt. For each node, this essentially does an "ssh host -l user prog [arg0] [arg1] ...". The -o option can be used to store stdout from each remote node in a directory. Each output file in that directory will be named by the corresponding remote node's hostname or IP address. """ import fcntl import os import sys parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) if os.path.exists(os.path.join(parent, 'psshlib')): sys.path.insert(0, parent) from psshlib import psshutil from psshlib.manager import Manager, FatalError from psshlib.task import Task from psshlib.cli import common_parser, common_defaults _DEFAULT_TIMEOUT = 60 def option_parser(): parser = common_parser() parser.usage = "%prog [OPTIONS] command [...]" parser.epilog = "Example: pssh -h hosts.txt -l irb2 -o /tmp/foo uptime" parser.add_option('-i', '--inline', dest='inline', action='store_true', help='inline aggregated output for each server') parser.add_option('-I', '--send-input', dest='send_input', action='store_true', help='read from standard input and send as input to ssh') parser.add_option('-P', '--print', dest='print_out', action='store_true', help='print output as we get it') return parser def parse_args(): parser = option_parser() defaults = common_defaults(timeout=_DEFAULT_TIMEOUT) parser.set_defaults(**defaults) opts, args = parser.parse_args() if len(args) == 0 and not opts.send_input: parser.error('Command not specified.') if not opts.host_files and not opts.host_strings: parser.error('Hosts not specified.') return opts, args def do_pssh(hosts, cmdline, opts): if opts.outdir and not os.path.exists(opts.outdir): os.makedirs(opts.outdir) if opts.errdir and not os.path.exists(opts.errdir): os.makedirs(opts.errdir) if opts.send_input: stdin = sys.stdin.read() else: stdin = None manager = Manager(opts) for host, port, user in hosts: cmd = ['ssh', host, '-o', 'NumberOfPasswordPrompts=1', '-o', 'SendEnv=PSSH_NODENUM'] if opts.options: for opt in opts.options: cmd += ['-o', opt] if user: cmd += ['-l', user] if port: cmd += ['-p', port] if opts.extra: cmd.extend(opts.extra) if cmdline: cmd.append(cmdline) t = Task(host, port, user, cmd, opts, stdin) manager.add_task(t) try: statuses = manager.run() except FatalError: sys.exit(1) if min(statuses) < 0: # At least one process was killed. sys.exit(3) # The any builtin was introduced in Python 2.5 (so we can't use it yet): #elif any(x==255 for x in statuses): for status in statuses: if status == 255: sys.exit(4) for status in statuses: if status != 0: sys.exit(5) if __name__ == "__main__": opts, args = parse_args() cmdline = " ".join(args) try: hosts = psshutil.read_host_files(opts.host_files, default_user=opts.user) except IOError: _, e, _ = sys.exc_info() sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) sys.exit(1) if opts.host_strings: for s in opts.host_strings: hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) do_pssh(hosts, cmdline, opts) pssh-2.2.2/bin/pslurp0000755002342500234200000000772111522362206014702 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun """Parallel scp from the set of nodes in hosts.txt. For each node, we essentially do a scp [-r] user@host:remote outdir//local. This program also uses the -q (quiet) and -C (compression) options. Note that remote must be an absolute path. """ import os import re import sys parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) if os.path.exists(os.path.join(parent, 'psshlib')): sys.path.insert(0, parent) from psshlib import psshutil from psshlib.task import Task from psshlib.manager import Manager, FatalError from psshlib.cli import common_parser, common_defaults def option_parser(): parser = common_parser() parser.usage = "%prog [OPTIONS] -h hosts.txt remote local" parser.epilog = ("Example: pslurp -h hosts.txt -L /tmp/outdir -l irb2 " + " /home/irb2/foo.txt foo.txt") parser.add_option('-r', '--recursive', dest='recursive', action='store_true', help='recusively copy directories (OPTIONAL)') parser.add_option('-L', '--localdir', dest='localdir', help='output directory for remote file copies') return parser def parse_args(): parser = option_parser() defaults = common_defaults() parser.set_defaults(**defaults) opts, args = parser.parse_args() if len(args) < 1: parser.error('Paths not specified.') if len(args) < 2: parser.error('Local path not specified.') if len(args) > 2: parser.error('Extra arguments given after the local path.') if not opts.host_files and not opts.host_strings: parser.error('Hosts not specified.') return opts, args def do_pslurp(hosts, remote, local, opts): if opts.localdir and not os.path.exists(opts.localdir): os.makedirs(opts.localdir) if opts.outdir and not os.path.exists(opts.outdir): os.makedirs(opts.outdir) if opts.errdir and not os.path.exists(opts.errdir): os.makedirs(opts.errdir) for host, port, user in hosts: if opts.localdir: dirname = "%s/%s" % (opts.localdir, host) else: dirname = host if not os.path.exists(dirname): os.mkdir(dirname) manager = Manager(opts) for host, port, user in hosts: if opts.localdir: localpath = "%s/%s/%s" % (opts.localdir, host, local) else: localpath = "%s/%s" % (host, local) cmd = ['scp', '-qC'] if opts.options: for opt in opts.options: cmd += ['-o', opt] if port: cmd += ['-P', port] if opts.recursive: cmd.append('-r') if opts.extra: cmd.extend(opts.extra) if user: cmd.append('%s@%s:%s' % (user, host, remote)) else: cmd.append('%s:%s' % (host, remote)) cmd.append(localpath) t = Task(host, port, user, cmd, opts) manager.add_task(t) try: statuses = manager.run() except FatalError: sys.exit(1) if min(statuses) < 0: # At least one process was killed. sys.exit(3) for status in statuses: if status == 255: sys.exit(4) for status in statuses: if status != 0: sys.exit(5) if __name__ == "__main__": opts, args = parse_args() remote = args[0] local = args[1] if not re.match("^/", remote): print("Remote path %s must be an absolute path" % remote) sys.exit(3) try: hosts = psshutil.read_host_files(opts.host_files, default_user=opts.user) except IOError: _, e, _ = sys.exc_info() sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) sys.exit(1) if opts.host_strings: for s in opts.host_strings: hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) do_pslurp(hosts, remote, local, opts) pssh-2.2.2/bin/pscp0000755002342500234200000000625611522362206014324 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun """Parallel scp to the set of nodes in hosts.txt. For each node, we essentially do a scp [-r] local user@host:remote. This program also uses the -q (quiet) and -C (compression) options. Note that remote must be an absolute path. """ import os import re import sys parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) if os.path.exists(os.path.join(parent, 'psshlib')): sys.path.insert(0, parent) from psshlib import psshutil from psshlib.task import Task from psshlib.manager import Manager, FatalError from psshlib.cli import common_parser, common_defaults def option_parser(): parser = common_parser() parser.usage = "%prog [OPTIONS] -h hosts.txt local remote" parser.epilog = ("Example: pscp -h hosts.txt -l irb2 foo.txt " + "/home/irb2/foo.txt") parser.add_option('-r', '--recursive', dest='recursive', action='store_true', help='recusively copy directories (OPTIONAL)') return parser def parse_args(): parser = option_parser() defaults = common_defaults() parser.set_defaults(**defaults) opts, args = parser.parse_args() if len(args) < 1: parser.error('Paths not specified.') if len(args) < 2: parser.error('Remote path not specified.') if not opts.host_files and not opts.host_strings: parser.error('Hosts not specified.') return opts, args def do_pscp(hosts, localargs, remote, opts): if opts.outdir and not os.path.exists(opts.outdir): os.makedirs(opts.outdir) if opts.errdir and not os.path.exists(opts.errdir): os.makedirs(opts.errdir) manager = Manager(opts) for host, port, user in hosts: cmd = ['scp', '-qC'] if opts.options: for opt in opts.options: cmd += ['-o', opt] if port: cmd += ['-P', port] if opts.recursive: cmd.append('-r') if opts.extra: cmd.extend(opts.extra) cmd.extend(localargs) if user: cmd.append('%s@%s:%s' % (user, host, remote)) else: cmd.append('%s:%s' % (host, remote)) t = Task(host, port, user, cmd, opts) manager.add_task(t) try: statuses = manager.run() except FatalError: sys.exit(1) if min(statuses) < 0: # At least one process was killed. sys.exit(3) for status in statuses: if status != 0: sys.exit(4) if __name__ == "__main__": opts, args = parse_args() localargs = args[0:-1] remote = args[-1] if not re.match("^/", remote): print("Remote path %s must be an absolute path" % remote) sys.exit(3) try: hosts = psshutil.read_host_files(opts.host_files, default_user=opts.user) except IOError: _, e, _ = sys.exc_info() sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) sys.exit(1) if opts.host_strings: for s in opts.host_strings: hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) do_pscp(hosts, localargs, remote, opts) pssh-2.2.2/AUTHORS0000644002342500234200000000011011270130553013706 0ustar amcnabbaml00000000000000Andrew McNabb Brent Chun pssh-2.2.2/psshlib/0000755002342500234200000000000011522363020014307 5ustar amcnabbaml00000000000000pssh-2.2.2/psshlib/askpass_server.py0000644002342500234200000000561411512245470017731 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb """Sends the password over a socket to askpass. """ import errno import getpass import os import socket import sys import tempfile import textwrap from psshlib import psshutil class PasswordServer(object): """Listens on a UNIX domain socket for password requests.""" def __init__(self): self.sock = None self.tempdir = None self.address = None self.socketmap = {} self.buffermap = {} def start(self, iomap, backlog): """Prompts for the password, creates a socket, and starts listening. The specified backlog should be the max number of clients connecting at once. """ message = ('Warning: do not enter your password if anyone else has' ' superuser privileges or access to your account.') print(textwrap.fill(message)) self.password = getpass.getpass() # Note that according to the docs for mkdtemp, "The directory is # readable, writable, and searchable only by the creating user." self.tempdir = tempfile.mkdtemp(prefix='pssh.') self.address = os.path.join(self.tempdir, 'pssh_askpass_socket') self.sock = socket.socket(socket.AF_UNIX) psshutil.set_cloexec(self.sock) self.sock.bind(self.address) self.sock.listen(backlog) iomap.register_read(self.sock.fileno(), self.handle_listen) def handle_listen(self, fd, iomap): try: conn = self.sock.accept()[0] except socket.error: _, e, _ = sys.exc_info() number = e.args[0] if number == errno.EINTR: return else: # TODO: print an error message here? self.sock.close() self.sock = None fd = conn.fileno() iomap.register_write(fd, self.handle_write) self.socketmap[fd] = conn self.buffermap[fd] = self.password def handle_write(self, fd, iomap): buffer = self.buffermap[fd] conn = self.socketmap[fd] try: bytes_written = conn.send(buffer) except socket.error: _, e, _ = sys.exc_info() number = e.args[0] if number == errno.EINTR: return else: self.close_socket(fd, iomap) buffer = buffer[bytes_written:] if buffer: self.buffermap[fd] = buffer else: self.close_socket(fd, iomap) def close_socket(self, fd, iomap): iomap.unregister(fd) self.socketmap[fd].close() del self.socketmap[fd] del self.buffermap[fd] def __del__(self): if self.sock: self.sock.close() self.sock = None if self.address: os.remove(self.address) if self.tempdir: os.rmdir(self.tempdir) pssh-2.2.2/psshlib/cli.py0000644002342500234200000001030511516376641015447 0ustar amcnabbaml00000000000000# Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun import optparse import os import shlex import sys import textwrap _DEFAULT_PARALLELISM = 32 _DEFAULT_TIMEOUT = 0 # "infinity" by default def common_parser(): """ Create a basic OptionParser with arguments common to all pssh programs. """ # The "resolve" conflict handler avoids errors from the hosts option # conflicting with the help option. parser = optparse.OptionParser(conflict_handler='resolve') # Ensure that options appearing after the command are sent to ssh. parser.disable_interspersed_args() parser.epilog = "Example: pssh -h nodes.txt -l irb2 -o /tmp/foo uptime" parser.add_option('-h', '--hosts', dest='host_files', action='append', metavar='HOST_FILE', help='hosts file (each line "[user@]host[:port]")') parser.add_option('-H', '--host', dest='host_strings', action='append', metavar='HOST_STRING', help='additional host entries ("[user@]host[:port]")') parser.add_option('-l', '--user', dest='user', help='username (OPTIONAL)') parser.add_option('-p', '--par', dest='par', type='int', help='max number of parallel threads (OPTIONAL)') parser.add_option('-o', '--outdir', dest='outdir', help='output directory for stdout files (OPTIONAL)') parser.add_option('-e', '--errdir', dest='errdir', help='output directory for stderr files (OPTIONAL)') parser.add_option('-t', '--timeout', dest='timeout', type='int', help='timeout (secs) (0 = no timeout) per host (OPTIONAL)') parser.add_option('-O', '--option', dest='options', action='append', metavar='OPTION', help='SSH option (OPTIONAL)') parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='turn on warning and diagnostic messages (OPTIONAL)') parser.add_option('-A', '--askpass', dest='askpass', action='store_true', help='Ask for a password (OPTIONAL)') parser.add_option('-x', '--extra-args', action='callback', type='string', metavar='ARGS', callback=shlex_append, dest='extra', help='Extra command-line arguments, with processing for ' 'spaces, quotes, and backslashes') parser.add_option('-X', '--extra-arg', dest='extra', action='append', metavar='ARG', help='Extra command-line argument') return parser def common_defaults(**kwargs): defaults = dict(par=_DEFAULT_PARALLELISM, timeout=_DEFAULT_TIMEOUT) defaults.update(**kwargs) envvars = [('user', 'PSSH_USER'), ('par', 'PSSH_PAR'), ('outdir', 'PSSH_OUTDIR'), ('errdir', 'PSSH_ERRDIR'), ('timeout', 'PSSH_TIMEOUT'), ('verbose', 'PSSH_VERBOSE'), ('print_out', 'PSSH_PRINT'), ('askpass', 'PSSH_ASKPASS'), ('inline', 'PSSH_INLINE'), ('recursive', 'PSSH_RECURSIVE'), ('archive', 'PSSH_ARCHIVE'), ('compress', 'PSSH_COMPRESS'), ('localdir', 'PSSH_LOCALDIR'), ] for option, var, in envvars: value = os.getenv(var) if value: defaults[option] = value value = os.getenv('PSSH_OPTIONS') if value: defaults['options'] = [value] value = os.getenv('PSSH_HOSTS') if value: message1 = ('Warning: the PSSH_HOSTS environment variable is ' 'deprecated. Please use the "-h" option instead, and consider ' 'creating aliases for convenience. For example:') message2 = " alias pssh_abc='pssh -h /path/to/hosts_abc'" sys.stderr.write(textwrap.fill(message1)) sys.stderr.write('\n') sys.stderr.write(message2) sys.stderr.write('\n') defaults['host_files'] = [value] return defaults def shlex_append(option, opt_str, value, parser): """An optparse callback similar to the append action. The given value is processed with shlex, and the resulting list is concatenated to the option's dest list. """ lst = getattr(parser.values, option.dest) if lst is None: lst = [] setattr(parser.values, option.dest, lst) lst.extend(shlex.split(value)) pssh-2.2.2/psshlib/__init__.py0000644002342500234200000000000011131470763016417 0ustar amcnabbaml00000000000000pssh-2.2.2/psshlib/task.py0000644002342500234200000002311111520112221015612 0ustar amcnabbaml00000000000000# Copyright (c) 2009, Andrew McNabb from errno import EINTR from subprocess import Popen, PIPE import os import signal import sys import time import traceback from psshlib import askpass_client from psshlib import color BUFFER_SIZE = 1 << 16 try: bytes except NameError: bytes = str class Task(object): """Starts a process and manages its input and output. Upon completion, the `exitstatus` attribute is set to the exit status of the process. """ def __init__(self, host, port, user, cmd, opts, stdin=None): self.exitstatus = None self.host = host self.pretty_host = host self.port = port self.cmd = cmd if user != opts.user: self.pretty_host = '@'.join((user, self.pretty_host)) if port: self.pretty_host = ':'.join((self.pretty_host, port)) self.proc = None self.writer = None self.timestamp = None self.failures = [] self.killed = False self.inputbuffer = stdin self.byteswritten = 0 self.outputbuffer = bytes() self.errorbuffer = bytes() self.stdin = None self.stdout = None self.stderr = None self.outfile = None self.errfile = None # Set options. self.verbose = opts.verbose try: self.print_out = bool(opts.print_out) except AttributeError: self.print_out = False try: self.inline = bool(opts.inline) except AttributeError: self.inline = False def start(self, nodenum, iomap, writer, askpass_socket=None): """Starts the process and registers files with the IOMap.""" self.writer = writer if writer: self.outfile, self.errfile = writer.open_files(self.pretty_host) # Set up the environment. environ = dict(os.environ) environ['PSSH_NODENUM'] = str(nodenum) # Disable the GNOME pop-up password dialog and allow ssh to use # askpass.py to get a provided password. If the module file is # askpass.pyc, we replace the extension. environ['SSH_ASKPASS'] = askpass_client.executable_path() if askpass_socket: environ['PSSH_ASKPASS_SOCKET'] = askpass_socket # Work around a mis-feature in ssh where it won't call SSH_ASKPASS # if DISPLAY is unset. if 'DISPLAY' not in environ: environ['DISPLAY'] = 'pssh-gibberish' # Create the subprocess. Since we carefully call set_cloexec() on # all open files, we specify close_fds=False. self.proc = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False, preexec_fn=os.setsid, env=environ) self.timestamp = time.time() if self.inputbuffer: self.stdin = self.proc.stdin iomap.register_write(self.stdin.fileno(), self.handle_stdin) else: self.proc.stdin.close() self.stdout = self.proc.stdout iomap.register_read(self.stdout.fileno(), self.handle_stdout) self.stderr = self.proc.stderr iomap.register_read(self.stderr.fileno(), self.handle_stderr) def _kill(self): """Signals the process to terminate.""" if self.proc: try: os.kill(-self.proc.pid, signal.SIGKILL) except OSError: # If the kill fails, then just assume the process is dead. pass self.killed = True def timedout(self): """Kills the process and registers a timeout error.""" if not self.killed: self._kill() self.failures.append('Timed out') def interrupted(self): """Kills the process and registers an keyboard interrupt error.""" if not self.killed: self._kill() self.failures.append('Interrupted') def cancel(self): """Stops a task that has not started.""" self.failures.append('Cancelled') def elapsed(self): """Finds the time in seconds since the process was started.""" return time.time() - self.timestamp def running(self): """Finds if the process has terminated and saves the return code.""" if self.stdin or self.stdout or self.stderr: return True if self.proc: self.exitstatus = self.proc.poll() if self.exitstatus is None: if self.killed: # Set the exitstatus to what it would be if we waited. self.exitstatus = -signal.SIGKILL return False else: return True else: if self.exitstatus < 0: message = 'Killed by signal %s' % (-self.exitstatus) self.failures.append(message) elif self.exitstatus > 0: message = 'Exited with error code %s' % self.exitstatus self.failures.append(message) self.proc = None return False def handle_stdin(self, fd, iomap): """Called when the process's standard input is ready for writing.""" try: start = self.byteswritten if start < len(self.inputbuffer): chunk = self.inputbuffer[start:start+BUFFER_SIZE] self.byteswritten = start + os.write(fd, chunk) else: self.close_stdin(iomap) except (OSError, IOError): _, e, _ = sys.exc_info() if e.errno != EINTR: self.close_stdin(iomap) self.log_exception(e) def close_stdin(self, iomap): if self.stdin: iomap.unregister(self.stdin.fileno()) self.stdin.close() self.stdin = None def handle_stdout(self, fd, iomap): """Called when the process's standard output is ready for reading.""" try: buf = os.read(fd, BUFFER_SIZE) if buf: if self.inline: self.outputbuffer += buf if self.outfile: self.writer.write(self.outfile, buf) if self.print_out: sys.stdout.write('%s: %s' % (self.host, buf)) if buf[-1] != '\n': sys.stdout.write('\n') else: self.close_stdout(iomap) except (OSError, IOError): _, e, _ = sys.exc_info() if e.errno != EINTR: self.close_stdout(iomap) self.log_exception(e) def close_stdout(self, iomap): if self.stdout: iomap.unregister(self.stdout.fileno()) self.stdout.close() self.stdout = None if self.outfile: self.writer.close(self.outfile) self.outfile = None def handle_stderr(self, fd, iomap): """Called when the process's standard error is ready for reading.""" try: buf = os.read(fd, BUFFER_SIZE) if buf: if self.inline: self.errorbuffer += buf if self.errfile: self.writer.write(self.errfile, buf) else: self.close_stderr(iomap) except (OSError, IOError): _, e, _ = sys.exc_info() if e.errno != EINTR: self.close_stderr(iomap) self.log_exception(e) def close_stderr(self, iomap): if self.stderr: iomap.unregister(self.stderr.fileno()) self.stderr.close() self.stderr = None if self.errfile: self.writer.close(self.errfile) self.errfile = None def log_exception(self, e): """Saves a record of the most recent exception for error reporting.""" if self.verbose: exc_type, exc_value, exc_traceback = sys.exc_info() exc = ("Exception: %s, %s, %s" % (exc_type, exc_value, traceback.format_tb(exc_traceback))) else: exc = str(e) self.failures.append(exc) def report(self, n): """Pretty prints a status report after the Task completes.""" error = ', '.join(self.failures) tstamp = time.asctime().split()[3] # Current time if color.has_colors(sys.stdout): progress = color.c("[%s]" % color.B(n)) success = color.g("[%s]" % color.B("SUCCESS")) failure = color.r("[%s]" % color.B("FAILURE")) stderr = color.r("Stderr: ") error = color.r(color.B(error)) else: progress = "[%s]" % n success = "[SUCCESS]" failure = "[FAILURE]" stderr = "Stderr: " host = self.pretty_host if self.failures: print(' '.join((progress, tstamp, failure, host, error))) else: print(' '.join((progress, tstamp, success, host))) # NOTE: The extra flushes are to ensure that the data is output in # the correct order with the C implementation of io. if self.outputbuffer: sys.stdout.flush() try: sys.stdout.buffer.write(self.outputbuffer) sys.stdout.flush() except AttributeError: sys.stdout.write(self.outputbuffer) if self.errorbuffer: sys.stdout.write(stderr) # Flush the TextIOWrapper before writing to the binary buffer. sys.stdout.flush() try: sys.stdout.buffer.write(self.errorbuffer) except AttributeError: sys.stdout.write(self.errorbuffer) pssh-2.2.2/psshlib/manager.py0000644002342500234200000002616311522362354016314 0ustar amcnabbaml00000000000000# Copyright (c) 2009, Andrew McNabb from errno import EINTR import os import select import signal import sys import threading try: import queue except ImportError: import Queue as queue from psshlib.askpass_server import PasswordServer from psshlib import psshutil READ_SIZE = 1 << 16 class FatalError(RuntimeError): """A fatal error in the PSSH Manager.""" pass class Manager(object): """Executes tasks concurrently. Tasks are added with add_task() and executed in parallel with run(). Returns a list of the exit statuses of the processes. Arguments: limit: Maximum number of commands running at once. timeout: Maximum allowed execution time in seconds. """ def __init__(self, opts): self.limit = opts.par self.timeout = opts.timeout self.askpass = opts.askpass self.outdir = opts.outdir self.errdir = opts.errdir self.iomap = IOMap() self.taskcount = 0 self.tasks = [] self.running = [] self.done = [] self.askpass_socket = None def run(self): """Processes tasks previously added with add_task.""" try: if self.outdir or self.errdir: writer = Writer(self.outdir, self.errdir) writer.start() else: writer = None if self.askpass: pass_server = PasswordServer() pass_server.start(self.iomap, self.limit) self.askpass_socket = pass_server.address self.set_sigchld_handler() try: self.update_tasks(writer) wait = None while self.running or self.tasks: # Opt for efficiency over subsecond timeout accuracy. if wait is None or wait < 1: wait = 1 self.iomap.poll(wait) self.update_tasks(writer) wait = self.check_timeout() except KeyboardInterrupt: # This exception handler tries to clean things up and prints # out a nice status message for each interrupted host. self.interrupted() except KeyboardInterrupt: # This exception handler doesn't print out any fancy status # information--it just stops. pass if writer: writer.signal_quit() writer.join() statuses = [task.exitstatus for task in self.done] return statuses def clear_sigchld_handler(self): signal.signal(signal.SIGCHLD, signal.SIG_DFL) def set_sigchld_handler(self): # TODO: find out whether set_wakeup_fd still works if the default # signal handler is used (I'm pretty sure it doesn't work if the # signal is ignored). signal.signal(signal.SIGCHLD, self.handle_sigchld) # This should keep reads and writes from getting EINTR. if hasattr(signal, 'siginterrupt'): signal.siginterrupt(signal.SIGCHLD, False) def handle_sigchld(self, number, frame): """Apparently we need a sigchld handler to make set_wakeup_fd work.""" # Write to the signal pipe (only for Python <2.5, where the # set_wakeup_fd method doesn't exist). if self.iomap.wakeup_writefd: os.write(self.iomap.wakeup_writefd, '\0') for task in self.running: if task.proc: task.proc.poll() # Apparently some UNIX systems automatically resent the SIGCHLD # handler to SIG_DFL. Reset it just in case. self.set_sigchld_handler() def add_task(self, task): """Adds a Task to be processed with run().""" self.tasks.append(task) def update_tasks(self, writer): """Reaps tasks and starts as many new ones as allowed.""" # Mask signals to work around a Python bug: # http://bugs.python.org/issue1068268 # Since sigprocmask isn't in the stdlib, clear the SIGCHLD handler. # Since signals are masked, reap_tasks needs to be called once for # each loop. keep_running = True while keep_running: self.clear_sigchld_handler() self._start_tasks_once(writer) self.set_sigchld_handler() keep_running = self.reap_tasks() def _start_tasks_once(self, writer): """Starts tasks once. Due to http://bugs.python.org/issue1068268, signals must be masked when this method is called. """ while 0 < len(self.tasks) and len(self.running) < self.limit: task = self.tasks.pop(0) self.running.append(task) task.start(self.taskcount, self.iomap, writer, self.askpass_socket) self.taskcount += 1 def reap_tasks(self): """Checks to see if any tasks have terminated. After cleaning up, returns the number of tasks that finished. """ still_running = [] finished_count = 0 for task in self.running: if task.running(): still_running.append(task) else: self.finished(task) finished_count += 1 self.running = still_running return finished_count def check_timeout(self): """Kills timed-out processes and returns the lowest time left.""" if self.timeout <= 0: return None min_timeleft = None for task in self.running: timeleft = self.timeout - task.elapsed() if timeleft <= 0: task.timedout() continue if min_timeleft is None or timeleft < min_timeleft: min_timeleft = timeleft if min_timeleft is None: return 0 else: return max(0, min_timeleft) def interrupted(self): """Cleans up after a keyboard interrupt.""" for task in self.running: task.interrupted() self.finished(task) for task in self.tasks: task.cancel() self.finished(task) def finished(self, task): """Marks a task as complete and reports its status to stdout.""" self.done.append(task) n = len(self.done) task.report(n) class IOMap(object): """A manager for file descriptors and their associated handlers. The poll method dispatches events to the appropriate handlers. """ def __init__(self): self.readmap = {} self.writemap = {} # Setup the wakeup file descriptor to avoid hanging on lost signals. wakeup_readfd, wakeup_writefd = os.pipe() self.register_read(wakeup_readfd, self.wakeup_handler) # TODO: remove test when we stop supporting Python <2.5 if hasattr(signal, 'set_wakeup_fd'): signal.set_wakeup_fd(wakeup_writefd) self.wakeup_writefd = None else: self.wakeup_writefd = wakeup_writefd def register_read(self, fd, handler): """Registers an IO handler for a file descriptor for reading.""" self.readmap[fd] = handler def register_write(self, fd, handler): """Registers an IO handler for a file descriptor for writing.""" self.writemap[fd] = handler def unregister(self, fd): """Unregisters the given file descriptor.""" if fd in self.readmap: del self.readmap[fd] if fd in self.writemap: del self.writemap[fd] def poll(self, timeout=None): """Performs a poll and dispatches the resulting events.""" if not self.readmap and not self.writemap: return rlist = list(self.readmap) wlist = list(self.writemap) try: rlist, wlist, _ = select.select(rlist, wlist, [], timeout) except select.error: _, e, _ = sys.exc_info() errno = e.args[0] if errno == EINTR: return else: raise for fd in rlist: handler = self.readmap[fd] handler(fd, self) for fd in wlist: handler = self.writemap[fd] handler(fd, self) def wakeup_handler(self, fd, iomap): """Handles read events on the signal wakeup pipe. This ensures that SIGCHLD signals aren't lost. """ try: os.read(fd, READ_SIZE) except (OSError, IOError): _, e, _ = sys.exc_info() errno, message = e.args if errno != EINTR: sys.stderr.write('Fatal error reading from wakeup pipe: %s\n' % message) raise FatalError class Writer(threading.Thread): """Thread that writes to files by processing requests from a Queue. Until AIO becomes widely available, it is impossible to make a nonblocking write to an ordinary file. The Writer thread processes all writing to ordinary files so that the main thread can work without blocking. """ OPEN = object() EOF = object() ABORT = object() def __init__(self, outdir, errdir): threading.Thread.__init__(self) # A daemon thread automatically dies if the program is terminated. self.setDaemon(True) self.queue = queue.Queue() self.outdir = outdir self.errdir = errdir self.host_counts = {} self.files = {} def run(self): while True: filename, data = self.queue.get() if filename == self.ABORT: return if data == self.OPEN: self.files[filename] = open(filename, 'wb', buffering=1) psshutil.set_cloexec(self.files[filename]) else: dest = self.files[filename] if data == self.EOF: dest.close() else: dest.write(data) def open_files(self, host): """Called from another thread to create files for stdout and stderr. Returns a pair of filenames (outfile, errfile). These filenames are used as handles for future operations. Either or both may be None if outdir or errdir or not set. """ outfile = errfile = None if self.outdir or self.errdir: count = self.host_counts.get(host, 0) self.host_counts[host] = count + 1 if count: filename = "%s.%s" % (host, count) else: filename = host if self.outdir: outfile = os.path.join(self.outdir, filename) self.queue.put((outfile, self.OPEN)) if self.errdir: errfile = os.path.join(self.errdir, filename) self.queue.put((errfile, self.OPEN)) return outfile, errfile def write(self, filename, data): """Called from another thread to enqueue a write.""" self.queue.put((filename, data)) def close(self, filename): """Called from another thread to close the given file.""" self.queue.put((filename, self.EOF)) def signal_quit(self): """Called from another thread to request the Writer to quit.""" self.queue.put((self.ABORT, None)) pssh-2.2.2/psshlib/color.py0000644002342500234200000000250311443514711016006 0ustar amcnabbaml00000000000000# Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun def with_color(string, fg, bg=49): '''Given foreground/background ANSI color codes, return a string that, when printed, will format the supplied string using the supplied colors. ''' return "\x1b[%dm\x1b[%dm%s\x1b[39m\x1b[49m" % (fg, bg, string) def B(string): '''Returns a string that, when printed, will display the supplied string in ANSI bold. ''' return "\x1b[1m%s\x1b[22m" % string def r(string): return with_color(string, 31) # Red def g(string): return with_color(string, 32) # Green def y(string): return with_color(string, 33) # Yellow def b(string): return with_color(string, 34) # Blue def m(string): return with_color(string, 35) # Magenta def c(string): return with_color(string, 36) # Cyan def w(string): return with_color(string, 37) # White #following from Python cookbook, #475186 def has_colors(stream): '''Returns boolean indicating whether or not the supplied stream supports ANSI color. ''' if not hasattr(stream, "isatty"): return False if not stream.isatty(): return False # auto color only on TTYs try: import curses curses.setupterm() return curses.tigetnum("colors") > 2 except: # guess false in case of error return False pssh-2.2.2/psshlib/psshutil.py0000644002342500234200000000610511520112173016535 0ustar amcnabbaml00000000000000# Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun import fcntl import string import sys HOST_FORMAT = 'Host format is [user@]host[:port] [user]' def read_host_files(paths, default_user=None, default_port=None): """Reads the given host files. Returns a list of (host, port, user) triples. """ hosts = [] if paths: for path in paths: hosts.extend(read_host_file(path, default_user=default_user)) return hosts def read_host_file(path, default_user=None, default_port=None): """Reads the given host file. Lines are of the form: host[:port] [login]. Returns a list of (host, port, user) triples. """ lines = [] f = open(path) for line in f: lines.append(line.strip()) f.close() hosts = [] for line in lines: # Skip blank lines or lines starting with # line = line.strip() if not line or line.startswith('#'): continue host, port, user = parse_host_entry(line, default_user, default_port) if host: hosts.append((host, port, user)) return hosts # TODO: deprecate the second host field and standardize on the # [user@]host[:port] format. def parse_host_entry(line, default_user, default_port): """Parses a single host entry. This may take either the of the form [user@]host[:port] or host[:port][ user]. Returns a (host, port, user) triple. """ fields = line.split() if len(fields) > 2: sys.stderr.write('Bad line: "%s". Format should be' ' [user@]host[:port] [user]\n' % line) return None, None, None host_field = fields[0] host, port, user = parse_host(host_field, default_port=default_port) if len(fields) == 2: if user is None: user = fields[1] else: sys.stderr.write('User specified twice in line: "%s"\n' % line) return None, None, None if user is None: user = default_user return host, port, user def parse_host_string(host_string, default_user=None, default_port=None): """Parses a whitespace-delimited string of "[user@]host[:port]" entries. Returns a list of (host, port, user) triples. """ hosts = [] entries = host_string.split() for entry in entries: hosts.append(parse_host(entry, default_user, default_port)) return hosts def parse_host(host, default_user=None, default_port=None): """Parses host entries of the form "[user@]host[:port]". Returns a (host, port, user) triple. """ # TODO: when we stop supporting Python 2.4, switch to using str.partition. user = default_user port = default_port if '@' in host: user, host = host.split('@', 1) if ':' in host: host, port = host.rsplit(':', 1) return (host, port, user) def set_cloexec(filelike): """Sets the underlying filedescriptor to automatically close on exec. If set_cloexec is called for all open files, then subprocess.Popen does not require the close_fds option. """ fcntl.fcntl(filelike.fileno(), fcntl.FD_CLOEXEC, 1) pssh-2.2.2/psshlib/askpass_client.py0000644002342500234200000000620311512701421017665 0ustar amcnabbaml00000000000000#!/usr/bin/env python # -*- Mode: python -*- # Copyright (c) 2009, Andrew McNabb """Implementation of SSH_ASKPASS to get a password to ssh from pssh. The password is read from the socket specified by the environment variable PSSH_ASKPASS_SOCKET. The other end of this socket is pssh. The ssh man page discusses SSH_ASKPASS as follows: If ssh needs a passphrase, it will read the passphrase from the current terminal if it was run from a terminal. If ssh does not have a terminal associated with it but DISPLAY and SSH_ASKPASS are set, it will execute the program specified by SSH_ASKPASS and open an X11 window to read the passphrase. This is particularly useful when calling ssh from a .xsession or related script. (Note that on some machines it may be necessary to redirect the input from /dev/null to make this work.) """ import os import socket import sys import textwrap bin_dir = os.path.dirname(os.path.abspath(sys.argv[0])) askpass_bin_path = os.path.join(bin_dir, 'pssh-askpass') ASKPASS_PATHS = (askpass_bin_path, '/usr/libexec/pssh/pssh-askpass', '/usr/local/libexec/pssh/pssh-askpass', '/usr/lib/pssh/pssh-askpass', '/usr/local/lib/pssh/pssh-askpass') _executable_path = None def executable_path(): """Determines the value to use for SSH_ASKPASS. The value is cached since this may be called many times. """ global _executable_path if _executable_path is None: for path in ASKPASS_PATHS: if os.access(path, os.X_OK): _executable_path = path break else: _executable_path = '' sys.stderr.write(textwrap.fill("Warning: could not find an" " executable path for askpass because PSSH was not" " installed correctly. Password prompts will not work.")) sys.stderr.write('\n') return _executable_path def askpass_main(): """Connects to pssh over the socket specified at PSSH_ASKPASS_SOCKET.""" # It's not documented anywhere, as far as I can tell, but ssh may prompt # for a password or ask a yes/no question. The command-line argument # specifies what is needed. if len(sys.argv) > 1: prompt = sys.argv[1] if not prompt.lower().endswith('password: '): sys.stderr.write(prompt) sys.stderr.write('\n') sys.exit(1) address = os.getenv('PSSH_ASKPASS_SOCKET') if not address: sys.stderr.write(textwrap.fill("pssh error: SSH requested a password." " Please create SSH keys or use the -A option to provide a" " password.")) sys.stderr.write('\n') sys.exit(1) sock = socket.socket(socket.AF_UNIX) try: sock.connect(address) except socket.error: _, e, _ = sys.exc_info() message = e.args[1] sys.stderr.write("Couldn't bind to %s: %s.\n" % (address, message)) sys.exit(2) try: password = sock.makefile().read() except socket.error: sys.stderr.write("Socket error.\n") sys.exit(3) print(password) if __name__ == '__main__': askpass_main() pssh-2.2.2/test/0000755002342500234200000000000011522363020013622 5ustar amcnabbaml00000000000000pssh-2.2.2/test/test.py0000644002342500234200000003050311270130554015160 0ustar amcnabbaml00000000000000#!/usr/bin/python # Copyright (c) 2009, Andrew McNabb # Copyright (c) 2003-2008, Brent N. Chun import os import sys import shutil import tempfile import time import unittest basedir, bin = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) sys.path.append("%s" % basedir) if os.getenv("TEST_HOSTS") is None: raise Exception("Must define TEST_HOSTS") g_hosts = os.getenv("TEST_HOSTS").split() if os.getenv("TEST_USER") is None: raise Exception("Must define TEST_USER") g_user = os.getenv("TEST_USER") class PsshTest(unittest.TestCase): def setUp(self): self.outDir = tempfile.mkdtemp() self.errDir = tempfile.mkdtemp() def teardown(self): shutil.rmtree(self.errDir) shutil.rmtree(self.outDir) def testShortOpts(self): hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pssh -h %s -l %s -p 64 -o %s -e %s -t 60 -v -P -i uptime < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: stdout = open("%s/%s" % (self.outDir, host)).read() self.assert_(stdout.find("load average") != -1) def testLongOpts(self): hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pssh --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --verbose --print --inline uptime < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: stdout = open("%s/%s" % (self.outDir, host)).read() self.assert_(stdout.find("load average") != -1) def testStderr(self): hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pssh -h %s -l %s -p 64 -o %s -e %s -t 60 -v -P -i ls /foobarbaz < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: stdout = open("%s/%s" % (self.outDir, host)).read() self.assertEqual(stdout, "") stderr = open("%s/%s" % (self.errDir, host)).read() self.assert_(stderr.find("No such file or directory") != -1) class PscpTest(unittest.TestCase): def setUp(self): self.outDir = tempfile.mkdtemp() self.errDir = tempfile.mkdtemp() def teardown(self): shutil.rmtree(self.errDir) shutil.rmtree(self.outDir) try: os.remove("/tmp/pssh.test") except OSError: pass def testShortOpts(self): for host in g_hosts: cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) rv = os.system(cmd) self.assertEqual(rv, 0) hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pscp -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, open("/etc/hosts").read()) def testLongOpts(self): for host in g_hosts: cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) rv = os.system(cmd) self.assertEqual(rv, 0) hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pscp --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, open("/etc/hosts").read()) def testRecursive(self): for host in g_hosts: cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) rv = os.system(cmd) self.assertEqual(rv, 0) hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pscp -r -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/init.d /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) files = os.popen("ls -R /etc/init.d | sed 1d | sort").read().strip() for host in g_hosts: cmd = "ssh %s@%s ls -R /tmp/pssh.test | sed 1d | sort" % (g_user, host) data = os.popen(cmd).read().strip() self.assertEqual(data, files) class PslurpTest(unittest.TestCase): def setUp(self): self.outDir = tempfile.mkdtemp() self.errDir = tempfile.mkdtemp() def teardown(self): shutil.rmtree(self.errDir) shutil.rmtree(self.outDir) def testShortOpts(self): if os.path.exists("/tmp/pssh.test"): try: os.remove("/tmp/pssh.test") except OSError: shutil.rmtree("/tmp/pssh.test") hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pslurp -L /tmp/pssh.test -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/hosts hosts < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s cat /etc/hosts" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, open("/tmp/pssh.test/%s/hosts" % host).read()) def testLongOpts(self): if os.path.exists("/tmp/pssh.test"): try: os.remove("/tmp/pssh.test") except OSError: shutil.rmtree("/tmp/pssh.test") hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pslurp --localdir=/tmp/pssh.test --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 /etc/hosts hosts < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s cat /etc/hosts" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, open("/tmp/pssh.test/%s/hosts" % host).read()) def testRecursive(self): if os.path.exists("/tmp/pssh.test"): try: os.remove("/tmp/pssh.test") except OSError: shutil.rmtree("/tmp/pssh.test") hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pslurp -r -L /tmp/pssh.test -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/init.d init.d < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s ls -R /etc/init.d | sed 1d | sort" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, os.popen("ls -R /tmp/pssh.test/%s/init.d | sed 1d | sort" % host).read()) class PrsyncTest(unittest.TestCase): def setUp(self): self.outDir = tempfile.mkdtemp() self.errDir = tempfile.mkdtemp() def teardown(self): shutil.rmtree(self.errDir) shutil.rmtree(self.outDir) def testShortOpts(self): for host in g_hosts: cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) rv = os.system(cmd) self.assertEqual(rv, 0) hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/prsync -h %s -l %s -p 64 -o %s -e %s -t 60 -a -z /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, open("/etc/hosts").read()) def testLongOpts(self): for host in g_hosts: cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) rv = os.system(cmd) self.assertEqual(rv, 0) hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/prsync --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --archive --compress /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) for host in g_hosts: cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) data = os.popen(cmd).read() self.assertEqual(data, open("/etc/hosts").read()) def testRecursive(self): for host in g_hosts: cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) rv = os.system(cmd) self.assertEqual(rv, 0) hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/prsync -r -h %s -l %s -p 64 -o %s -e %s -t 60 -a -z /etc/init.d/ /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) rv = os.system(cmd) self.assertEqual(rv, 0) files = os.popen("ls -R /etc/init.d | sed 1d | sort").read().strip() for host in g_hosts: cmd = "ssh %s@%s ls -R /tmp/pssh.test | sed 1d | sort" % (g_user, host) data = os.popen(cmd).read().strip() self.assertEqual(data, files) class PnukeTest(unittest.TestCase): def setUp(self): self.outDir = tempfile.mkdtemp() self.errDir = tempfile.mkdtemp() def teardown(self): shutil.rmtree(self.errDir) shutil.rmtree(self.outDir) def testShortOpts(self): hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pssh -h %s -l %s -p 64 -o %s -e %s -t 60 -v sleep 60 < /dev/null &" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) os.system(cmd) time.sleep(5) cmd = "%s/bin/pnuke -h %s -l %s -p 64 -o %s -e %s -t 60 -v sleep < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) print cmd rv = os.system(cmd) self.assertEqual(rv, 0) def testLongOpts(self): hostsFile = tempfile.NamedTemporaryFile() hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) hostsFile.flush() cmd = "%s/bin/pssh --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --verbose sleep 60 < /dev/null &" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) os.system(cmd) time.sleep(5) cmd = "%s/bin/pnuke --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --verbose sleep < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) print cmd rv = os.system(cmd) self.assertEqual(rv, 0) if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(PsshTest, "test")) suite.addTest(unittest.makeSuite(PscpTest, "test")) suite.addTest(unittest.makeSuite(PslurpTest, "test")) suite.addTest(unittest.makeSuite(PrsyncTest, "test")) suite.addTest(unittest.makeSuite(PnukeTest, "test")) unittest.TextTestRunner().run(suite)