diff options
| author | David Pursehouse <david.pursehouse@sonymobile.com> | 2013-09-13 19:03:24 +0900 |
|---|---|---|
| committer | David Pursehouse <david.pursehouse@sonymobile.com> | 2013-09-13 19:03:37 +0900 |
| commit | 87b68fc2db307fe4e294abd913bcee80ecb9cb7f (patch) | |
| tree | 70f1f534633cb128c780cc1b29ef685fe255798e /pygerrit/ssh.py | |
| parent | 8571778ed4ebd1ebacba2334d8b6b19fbcd5a2b9 (diff) | |
| parent | ae41ccac316c6bd45ec4af0146219212bbb08c87 (diff) | |
| download | pygerrit-87b68fc2db307fe4e294abd913bcee80ecb9cb7f.tar.gz | |
Merge branch 'master' into internal
* master:
Bump version to 0.1.1
Add changelog
Fix #10: Allow to manually specify ssh username and port
Completely refactor the stream event handling
Add missing __repr__ methods on ErrorEvent and UnhandledEvent
Fix initialisation of error event
Fix #11: correct handling of `identityfile` in the ssh config
Allow example script to continue if errors are received
Fix #9: Add a bit more detail in the documentation
Fix #8: Support the "topic-changed" stream event
Fix #7: Support the "reviewer-added" stream event
Fix #6: Support the "merge-failed" stream event
Fix #5: Move json parsing and error handling into the event factory
Improved logging in the example script
Fix #3: Don't treat unhandled event types as errors
Fix #4: Keep event's raw json data in the event object
Add __repr__ methods on event and model classes
Remove redundant `exec_command` method
Fix #2: Establish SSH connection in a thread-safe way
Fix #1: Use select.select() instead of select.poll()
Change-Id: Ib91384b16acca30bfc5aadcdc1131c8bdecef583
Diffstat (limited to 'pygerrit/ssh.py')
| -rw-r--r-- | pygerrit/ssh.py | 79 |
1 files changed, 49 insertions, 30 deletions
diff --git a/pygerrit/ssh.py b/pygerrit/ssh.py index 3e2add1..e779715 100644 --- a/pygerrit/ssh.py +++ b/pygerrit/ssh.py @@ -25,6 +25,7 @@ from os.path import abspath, expanduser, isfile import re import socket +from threading import Event, Lock from .error import GerritError @@ -64,18 +65,19 @@ class GerritSSHClient(SSHClient): """ Gerrit SSH Client, wrapping the paramiko SSH Client. """ - def __init__(self, hostname): + def __init__(self, hostname, username=None, port=None): """ Initialise and connect to SSH. """ super(GerritSSHClient, self).__init__() self.remote_version = None self.hostname = hostname - self.connected = False - - def _connect(self): - """ Connect to the remote if not already connected. """ - if self.connected: - return - self.load_system_host_keys() + self.username = username + self.key_filename = None + self.port = port + self.connected = Event() + self.lock = Lock() + + def _configure(self): + """ Configure the ssh parameters from the config file. """ configfile = expanduser("~/.ssh/config") if not isfile(configfile): raise GerritError("ssh config file '%s' does not exist" % @@ -88,22 +90,29 @@ class GerritSSHClient(SSHClient): raise GerritError("No ssh config for host %s" % self.hostname) if not 'hostname' in data or not 'port' in data or not 'user' in data: raise GerritError("Missing configuration data in %s" % configfile) - key_filename = None + self.hostname = data['hostname'] + self.username = data['user'] if 'identityfile' in data: - key_filename = abspath(expanduser(data['identityfile'])) + key_filename = abspath(expanduser(data['identityfile'][0])) if not isfile(key_filename): raise GerritError("Identity file '%s' does not exist" % key_filename) + self.key_filename = key_filename try: - port = int(data['port']) + self.port = int(data['port']) except ValueError: raise GerritError("Invalid port: %s" % data['port']) + + def _do_connect(self): + """ Connect to the remote. """ + self.load_system_host_keys() + if self.username is None or self.port is None: + self._configure() try: - self.connect(hostname=data['hostname'], - port=port, - username=data['user'], - key_filename=key_filename) - self.connected = True + self.connect(hostname=self.hostname, + port=self.port, + username=self.username, + key_filename=self.key_filename) except socket.error as e: raise GerritError("Failed to connect to server: %s" % e) @@ -114,17 +123,20 @@ class GerritSSHClient(SSHClient): except AttributeError: self.remote_version = None - def exec_command(self, command, bufsize=1, timeout=None, get_pty=False): - """ Execute the command. - - Make sure we're connected and then execute the command. - - Return a tuple of stdin, stdout, stderr. - - """ - self._connect() - return super(GerritSSHClient, self).\ - exec_command(command, bufsize, timeout, get_pty) + def _connect(self): + """ Connect to the remote if not already connected. """ + if not self.connected.is_set(): + try: + self.lock.acquire() + # Another thread may have connected while we were + # waiting to acquire the lock + if not self.connected.is_set(): + self._do_connect() + self.connected.set() + except GerritError: + raise + finally: + self.lock.release() def get_remote_version(self): """ Return the version of the remote Gerrit server. """ @@ -138,17 +150,24 @@ class GerritSSHClient(SSHClient): def run_gerrit_command(self, command): """ Run the given command. - Run `command` and return a `GerritSSHCommandResult`. + Make sure we're connected to the remote server, and run `command`. + + Return the results as a `GerritSSHCommandResult`. - Raise `ValueError` if `command` is not a string. + Raise `ValueError` if `command` is not a string, or `GerritError` if + command execution fails. """ if not isinstance(command, basestring): raise ValueError("command must be a string") gerrit_command = "gerrit " + command + self._connect() try: - stdin, stdout, stderr = self.exec_command(gerrit_command) + stdin, stdout, stderr = self.exec_command(gerrit_command, + bufsize=1, + timeout=None, + get_pty=False) except SSHException as err: raise GerritError("Command execution error: %s" % err) return GerritSSHCommandResult(command, stdin, stdout, stderr) |
