From 6c8f1f2219ad689c1f6e174552103e1354b6be55 Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 20:50:26 +0100 Subject: [PATCH 1/9] WIP: started working on subprocessing. Does not work --- sshmux/__init__.py | 2 +- sshmux/ssh.py | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/sshmux/__init__.py b/sshmux/__init__.py index 792d600..8b13789 100644 --- a/sshmux/__init__.py +++ b/sshmux/__init__.py @@ -1 +1 @@ -# + diff --git a/sshmux/ssh.py b/sshmux/ssh.py index a0f5c4f..9d3a73f 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -1,6 +1,6 @@ from __future__ import print_function -import pexpect +from subprocess import Popen, PIPE, STDOUT import tempfile from os import unlink from sshmux.errors import MuxError @@ -13,30 +13,24 @@ def print_output(server, output): print(line) -def ssh(host, cmd, user, password, key, timeout=10, bg_run=False): +def ssh(host, cmd, user, key, timeout=10, bg_run=False): """connect to host via ssh""" output_file = tempfile.NamedTemporaryFile(delete=False) option = ["-q", "-oStrictHostKeyChecking=no", - "-oUserKnownHostsFile=/dev/null"] - if password: - option.append("-oPubkeyAuthentication=no") - if not password: - option.append("-o PreferredAuthentications=publickey") + "-oUserKnownHostsFile=/dev/null", "-o PreferredAuthentications=publickey"] if bg_run: option.append('-f') - options = " ".join(option) ssh_cmd = None if not password: ssh_cmd = 'ssh -i {0} {1}@{2} {3} "{4}"'.format( key, user, host, options, cmd) - elif password: - ssh_cmd = 'ssh {0}@{1} {2} "{3}"'.format(user, host, options, cmd) - child = pexpect.spawn(ssh_cmd, timeout=timeout) - if password: - child.expect(['Password for']) - child.sendline(password) + run = Popen(ssh_cmd, stdout=PIPE, stderr=STDOUT, shell=True) + run.wait() + if not run.returncode: + raise MuxError("failed to run {0} on {1}".format(cmd, host)) + child.logfile = output_file child.expect(pexpect.EOF) From 4e7d41984893cd3059549729d78c02648d478c1a Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 21:10:09 +0100 Subject: [PATCH 2/9] WIP: code kind of works. one test still fails. --- sshmux/ssh.py | 50 +++++++++++++++++++++++------------------------ tests/test_ssh.py | 4 ++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/sshmux/ssh.py b/sshmux/ssh.py index 9d3a73f..0e349ff 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -13,38 +13,38 @@ def print_output(server, output): print(line) -def ssh(host, cmd, user, key, timeout=10, bg_run=False): +def ssh(host, cmd, user, key, wait=10, bg_run=False): """connect to host via ssh""" - output_file = tempfile.NamedTemporaryFile(delete=False) + # output_file = tempfile.NamedTemporaryFile(delete=False) option = ["-q", "-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null", "-o PreferredAuthentications=publickey"] if bg_run: option.append('-f') options = " ".join(option) ssh_cmd = None - if not password: - ssh_cmd = 'ssh -i {0} {1}@{2} {3} "{4}"'.format( - key, user, host, options, cmd) + ssh_cmd = 'ssh -i {0} {1}@{2} {3} "{4}"'.format( + key, user, host, options, cmd) run = Popen(ssh_cmd, stdout=PIPE, stderr=STDOUT, shell=True) run.wait() - if not run.returncode: - raise MuxError("failed to run {0} on {1}".format(cmd, host)) - - - child.logfile = output_file - child.expect(pexpect.EOF) - child.close() - output_file.close() - - # BUG(rjrhaverkamp): U mode is deprecated - read_file = open(output_file.name, 'rU') - stdout = read_file.read() - output_file.close() - read_file.close() - unlink(output_file.name) - - if child.exitstatus != 0: - raise MuxError(stdout) - print_output(host, stdout) - return stdout + # if not run.returncode: + # raise MuxError("failed to run {0} on {1}".format(cmd, host)) + + output, _ = run.communicate() + + # child.logfile = output_file + # child.expect(pexpect.EOF) + # child.close() + # output_file.close() + + # # BUG(rjrhaverkamp): U mode is deprecated + # read_file = open(output_file.name, 'rU') + # stdout = read_file.read() + # output_file.close() + # read_file.close() + # unlink(output_file.name) + + # if child.exitstatus != 0: + # raise MuxError(stdout) + print_output(host, output.decode("utf-8")) + return output.decode("utf-8") diff --git a/tests/test_ssh.py b/tests/test_ssh.py index fdb57ec..115a1f2 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -8,7 +8,7 @@ class TestSSH(unittest.TestCase): def test_ssh_output(self): output = ssh.ssh(environ['sshmux_test_host'], 'echo "hello"', environ[ - 'sshmux_test_user'], '', environ['sshmux_test_key']) + 'sshmux_test_user'], environ['sshmux_test_key']) self.assertEqual(output, 'hello\n') def test_wrong_cmd(self): @@ -16,7 +16,7 @@ def test_wrong_cmd(self): user = environ['sshmux_test_user'] key = environ['sshmux_test_key'] self.assertRaises(errors.MuxError, ssh.ssh, host, - 'does_not_exist', user, '', key) + 'does_not_exist', user, key) if __name__ == '__main__': unittest.main() From fd2190c8ec018341562c02a1bdb722f3c363664e Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 21:20:05 +0100 Subject: [PATCH 3/9] WIP: tests work and code runs. --- sshmux/ssh.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/sshmux/ssh.py b/sshmux/ssh.py index 0e349ff..e1d2dfa 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -1,8 +1,6 @@ from __future__ import print_function from subprocess import Popen, PIPE, STDOUT -import tempfile -from os import unlink from sshmux.errors import MuxError @@ -15,7 +13,6 @@ def print_output(server, output): def ssh(host, cmd, user, key, wait=10, bg_run=False): """connect to host via ssh""" - # output_file = tempfile.NamedTemporaryFile(delete=False) option = ["-q", "-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null", "-o PreferredAuthentications=publickey"] if bg_run: @@ -27,24 +24,12 @@ def ssh(host, cmd, user, key, wait=10, bg_run=False): run = Popen(ssh_cmd, stdout=PIPE, stderr=STDOUT, shell=True) run.wait() - # if not run.returncode: - # raise MuxError("failed to run {0} on {1}".format(cmd, host)) + + if run.returncode != 0: + raise MuxError("failed to run {0} on {1}. Exited with: {2}".format( + cmd, host, run.returncode)) output, _ = run.communicate() - - # child.logfile = output_file - # child.expect(pexpect.EOF) - # child.close() - # output_file.close() - - # # BUG(rjrhaverkamp): U mode is deprecated - # read_file = open(output_file.name, 'rU') - # stdout = read_file.read() - # output_file.close() - # read_file.close() - # unlink(output_file.name) - - # if child.exitstatus != 0: - # raise MuxError(stdout) + print(run.returncode) print_output(host, output.decode("utf-8")) return output.decode("utf-8") From bcac9ce5661e44814e7698b5f2a45d05bb94263e Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 22:01:36 +0100 Subject: [PATCH 4/9] WIP: remove password support and code cleanups --- sshmux/main.py | 11 +++-------- sshmux/ssh.py | 10 +++++----- sshmux/validate.py | 9 --------- tests/test_validations.py | 9 --------- 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/sshmux/main.py b/sshmux/main.py index 0ec0f48..3ee9a6c 100644 --- a/sshmux/main.py +++ b/sshmux/main.py @@ -14,16 +14,11 @@ multiple=True, help='IP address or hostname') @click.option('--username', '-u', callback=validate.validate_user, default='', help='ssh username') -@click.option('--password', '-p', default=False, help='ssh password') @click.option('--key', '-k', default=environ['HOME'] + '/.ssh/id_rsa', help='ssh private key') -def main(hostname, username, password, key): +def main(hostname, username, key): """Open ssh session with each ip and execute a command from stdin.""" - if not password: - key = validate.validate_key(key) - if password: - password = getpass() - password = validate.validate_pass(password) + key = validate.validate_key(key) print("Enter your commands below:\n") command = input("sshmux > ") @@ -31,7 +26,7 @@ def main(hostname, username, password, key): procs = [] for server in hostname: procs.append(multiprocessing.Process( - target=ssh, args=(server, command, username, password, key))) + target=ssh, args=(server, command, username, key))) for proc in procs: proc.start() for proc in procs: diff --git a/sshmux/ssh.py b/sshmux/ssh.py index e1d2dfa..0a17e36 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -11,7 +11,7 @@ def print_output(server, output): print(line) -def ssh(host, cmd, user, key, wait=10, bg_run=False): +def ssh(host, cmd, user, key, bg_run=False): """connect to host via ssh""" option = ["-q", "-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null", "-o PreferredAuthentications=publickey"] @@ -24,12 +24,12 @@ def ssh(host, cmd, user, key, wait=10, bg_run=False): run = Popen(ssh_cmd, stdout=PIPE, stderr=STDOUT, shell=True) run.wait() - + if run.returncode != 0: raise MuxError("failed to run {0} on {1}. Exited with: {2}".format( cmd, host, run.returncode)) output, _ = run.communicate() - print(run.returncode) - print_output(host, output.decode("utf-8")) - return output.decode("utf-8") + stdout = output.decode("utf-8") + print_output(host, stdout) + return diff --git a/sshmux/validate.py b/sshmux/validate.py index e291ede..7948ec1 100644 --- a/sshmux/validate.py +++ b/sshmux/validate.py @@ -18,21 +18,12 @@ def validate_hostname(ctx, param, hostname): '{0} - Address is not valid'.format(address)) return hostname - -def validate_pass(password): - """validate password lenght""" - if len(password) == 0 or len(password) > 100: - raise MuxError('password length is not valid') - return password - - def validate_user(ctx, param, username): """validate username length""" if len(username) == 0 or len(username) > 100: raise click.BadParameter('username length is not valid') return username - def validate_key(key_path): """validate that key exists.""" if path.exists(key_path): diff --git a/tests/test_validations.py b/tests/test_validations.py index 4a72afa..63a6f28 100644 --- a/tests/test_validations.py +++ b/tests/test_validations.py @@ -61,14 +61,5 @@ def test_key_fail(self): key = environ['HOME'] + '/.ssh/id_rsa_that_does_not_exist' self.assertRaises(MuxError, validate.validate_key, key) - def test_password_check(self): - password = "testpassword" - valid_password = validate.validate_pass(password) - self.assertEqual(password, valid_password) - - def test_password_fail(self): - self.assertRaises(MuxError, - validate.validate_pass, "testpassword" * 12) - if __name__ == '__main__': unittest.main() From d86e749c2137aa9bb596af76f3ab4204ebba6751 Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 22:11:10 +0100 Subject: [PATCH 5/9] flake8 was complaining. --- sshmux/__init__.py | 1 - sshmux/main.py | 1 - sshmux/ssh.py | 3 ++- sshmux/validate.py | 2 ++ tests/test_ssh.py | 1 + tests/test_validations.py | 10 +++++----- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/sshmux/__init__.py b/sshmux/__init__.py index 8b13789..e69de29 100644 --- a/sshmux/__init__.py +++ b/sshmux/__init__.py @@ -1 +0,0 @@ - diff --git a/sshmux/main.py b/sshmux/main.py index 3ee9a6c..92ea036 100644 --- a/sshmux/main.py +++ b/sshmux/main.py @@ -5,7 +5,6 @@ import multiprocessing from sshmux import validate from sshmux.ssh import ssh -from getpass import getpass from os import environ diff --git a/sshmux/ssh.py b/sshmux/ssh.py index 0a17e36..62619b6 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -14,7 +14,8 @@ def print_output(server, output): def ssh(host, cmd, user, key, bg_run=False): """connect to host via ssh""" option = ["-q", "-oStrictHostKeyChecking=no", - "-oUserKnownHostsFile=/dev/null", "-o PreferredAuthentications=publickey"] + "-oUserKnownHostsFile=/dev/null", + "-o PreferredAuthentications=publickey"] if bg_run: option.append('-f') options = " ".join(option) diff --git a/sshmux/validate.py b/sshmux/validate.py index 7948ec1..6bcf308 100644 --- a/sshmux/validate.py +++ b/sshmux/validate.py @@ -18,12 +18,14 @@ def validate_hostname(ctx, param, hostname): '{0} - Address is not valid'.format(address)) return hostname + def validate_user(ctx, param, username): """validate username length""" if len(username) == 0 or len(username) > 100: raise click.BadParameter('username length is not valid') return username + def validate_key(key_path): """validate that key exists.""" if path.exists(key_path): diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 115a1f2..43e038f 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -18,5 +18,6 @@ def test_wrong_cmd(self): self.assertRaises(errors.MuxError, ssh.ssh, host, 'does_not_exist', user, key) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_validations.py b/tests/test_validations.py index 63a6f28..07329ab 100644 --- a/tests/test_validations.py +++ b/tests/test_validations.py @@ -3,7 +3,6 @@ from sshmux import validate from sshmux.errors import MuxError from os import environ -import sys from click.testing import CliRunner @@ -11,14 +10,14 @@ class TestValidations(unittest.TestCase): @click.command() - @click.option('--hostname', '-h', callback=validate.validate_hostname, multiple=True, - help='IP address or hostname') + @click.option('--hostname', '-h', callback=validate.validate_hostname, + multiple=True, help='IP address or hostname') def check_hostname(hostname): click.echo('sucess') @click.command() - @click.option('--username', '-u', callback=validate.validate_user, default='', - help='ssh username') + @click.option('--username', '-u', callback=validate.validate_user, + default='', help='ssh username') def check_username(username): click.echo('sucess') @@ -61,5 +60,6 @@ def test_key_fail(self): key = environ['HOME'] + '/.ssh/id_rsa_that_does_not_exist' self.assertRaises(MuxError, validate.validate_key, key) + if __name__ == '__main__': unittest.main() From 2a9138973b03d8d3aa5ee9332af5ff0e956c529a Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 22:32:16 +0100 Subject: [PATCH 6/9] WIP: need to return stdout in ssh function for tests --- sshmux/ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sshmux/ssh.py b/sshmux/ssh.py index 62619b6..4385d7e 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -33,4 +33,4 @@ def ssh(host, cmd, user, key, bg_run=False): output, _ = run.communicate() stdout = output.decode("utf-8") print_output(host, stdout) - return + return stdout From cb721d140b21262f9cd8c222a4dbaa9774642ced Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sat, 14 Jan 2017 22:54:01 +0100 Subject: [PATCH 7/9] WIP: add background run test --- tests/test_ssh.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 43e038f..cb02886 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -19,5 +19,10 @@ def test_wrong_cmd(self): 'does_not_exist', user, key) + def test_background_run(self): + output = ssh.ssh(environ['sshmux_test_host'], 'echo "hello"&', environ[ + 'sshmux_test_user'], environ['sshmux_test_key'], bg_run=True) + self.assertEqual(output, 'hello\n') + if __name__ == '__main__': unittest.main() From a26cd2a693091c7b2d149c8a2e6b83a5ab1cd76b Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sun, 15 Jan 2017 15:23:38 +0100 Subject: [PATCH 8/9] add command not found error handling --- sshmux/ssh.py | 74 ++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/sshmux/ssh.py b/sshmux/ssh.py index 4385d7e..db729fb 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -1,36 +1,38 @@ -from __future__ import print_function - -from subprocess import Popen, PIPE, STDOUT -from sshmux.errors import MuxError - - -def print_output(server, output): - """parse ssh output and print to stdout""" - print(server + ":\n") - for line in output.split('\n')[1:]: - print(line) - - -def ssh(host, cmd, user, key, bg_run=False): - """connect to host via ssh""" - option = ["-q", "-oStrictHostKeyChecking=no", - "-oUserKnownHostsFile=/dev/null", - "-o PreferredAuthentications=publickey"] - if bg_run: - option.append('-f') - options = " ".join(option) - ssh_cmd = None - ssh_cmd = 'ssh -i {0} {1}@{2} {3} "{4}"'.format( - key, user, host, options, cmd) - - run = Popen(ssh_cmd, stdout=PIPE, stderr=STDOUT, shell=True) - run.wait() - - if run.returncode != 0: - raise MuxError("failed to run {0} on {1}. Exited with: {2}".format( - cmd, host, run.returncode)) - - output, _ = run.communicate() - stdout = output.decode("utf-8") - print_output(host, stdout) - return stdout +from __future__ import print_function + +from subprocess import Popen, PIPE, STDOUT +from sshmux.errors import MuxError + + +def print_output(server, output): + """parse ssh output and print to stdout""" + print(server + ":\n") + for line in output.split('\n')[1:]: + print(line) + + +def ssh(host, cmd, user, key, bg_run=False): + """connect to host via ssh""" + option = ["-q", "-oStrictHostKeyChecking=no", + "-oUserKnownHostsFile=/dev/null", + "-o PreferredAuthentications=publickey"] + if bg_run: + option.append('-f') + options = " ".join(option) + ssh_cmd = None + ssh_cmd = 'ssh -i {0} {1}@{2} {3} "{4}"'.format( + key, user, host, options, cmd) + + run = Popen(ssh_cmd, stdout=PIPE, stderr=STDOUT, shell=True) + run.wait() + + if run.returncode == 127: + raise MuxError("command {0} does not exist.".format(cmd)) + if run.returncode != 0: + raise MuxError("failed to run {0} on {1}. Exited with: {2}".format( + cmd, host, run.returncode)) + + output, _ = run.communicate() + stdout = output.decode("utf-8") + print_output(host, stdout) + return stdout From a84a689fb75888971c96e1bb5ee6513c21300206 Mon Sep 17 00:00:00 2001 From: rjrhaverkamp Date: Sun, 15 Jan 2017 15:28:28 +0100 Subject: [PATCH 9/9] fix flake8 errors --- sshmux/ssh.py | 4 ++-- tests/test_ssh.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/sshmux/ssh.py b/sshmux/ssh.py index db729fb..9017174 100644 --- a/sshmux/ssh.py +++ b/sshmux/ssh.py @@ -18,7 +18,7 @@ def ssh(host, cmd, user, key, bg_run=False): "-o PreferredAuthentications=publickey"] if bg_run: option.append('-f') - options = " ".join(option) + options = " ".join(option) ssh_cmd = None ssh_cmd = 'ssh -i {0} {1}@{2} {3} "{4}"'.format( key, user, host, options, cmd) @@ -27,7 +27,7 @@ def ssh(host, cmd, user, key, bg_run=False): run.wait() if run.returncode == 127: - raise MuxError("command {0} does not exist.".format(cmd)) + raise MuxError("command {0} does not exist.".format(cmd)) if run.returncode != 0: raise MuxError("failed to run {0} on {1}. Exited with: {2}".format( cmd, host, run.returncode)) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index cb02886..597111f 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -18,11 +18,10 @@ def test_wrong_cmd(self): self.assertRaises(errors.MuxError, ssh.ssh, host, 'does_not_exist', user, key) - def test_background_run(self): - output = ssh.ssh(environ['sshmux_test_host'], 'echo "hello"&', environ[ - 'sshmux_test_user'], environ['sshmux_test_key'], bg_run=True) + output = ssh.ssh(environ['sshmux_test_host'], 'echo "hello"&', environ['sshmux_test_user'], environ['sshmux_test_key'], bg_run=True) # NOQA self.assertEqual(output, 'hello\n') + if __name__ == '__main__': unittest.main()