# Copyright (C) 2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import collections import logging import re import urllib2 import urlparse import cliapp import lorrycontroller class GitanoCommandFailure(Exception): def __init__(self, trovehost, command, stderr): Exception.__init__( self, 'Failed to run "%s" on Gitano on %s\n%s' % (command, trovehost, stderr)) class GitanoCommand(object): '''Run a Gitano command on a Trove.''' def __init__(self, trovehost, protocol, username, password): self.trovehost = trovehost self.protocol = protocol self.username = username self.password = password if protocol == 'ssh': self._command = self._ssh_command elif protocol in ('http', 'https'): self._command = self._http_command else: raise GitanoCommandFailure( self.trovehost, '__init__', 'unknown protocol %s' % protocol) def whoami(self): return self._command(['whoami']) def create(self, repo_path): self._command(['create', repo_path]) def get_gitano_config(self, repo_path): stdout = self._command(['config', repo_path, 'show']) # "config REPO show" outputs a sequence of lines of the form "key: value". # Extract those into a collections.defaultdict. result = collections.defaultdict(str) for line in stdout.splitlines(): m = re.match(r'^([^:])+:\s*(.*)$', line) if m: result[m.group(0)] = m.group(1).strip() return result def set_gitano_config(self, path, key, value): self._command(['config', path, 'set', key, value]) def ls(self): return self._command(['ls']) def _ssh_command(self, gitano_args): quoted_args = [cliapp.shell_quote(x) for x in gitano_args] base_argv = [ 'ssh', '-oStrictHostKeyChecking=no', '-oBatchMode=yes', 'git@%s' % self.trovehost, ] exit, stdout, stderr = cliapp.runcmd_unchecked( base_argv + quoted_args) if exit != 0: logging.error( 'Failed to run "%s" for %s:\n%s', quoted_args, self.trovehost, stdout + stderr) raise GitanoCommandFailure( self.trovehost, ' '.join(gitano_args), stdout + stderr) return stdout def _http_command(self, gitano_args): quoted_args = urllib2.quote(' '.join(gitano_args)) url = urlparse.urlunsplit(( self.protocol, self.trovehost, '/gitano-command.cgi', 'cmd=%s' % quoted_args, '')) logging.debug('url=%r', url) try: request = urllib2.Request(url, None, {}) logging.debug('request=%r', request.get_full_url()) if self.username and self.password: password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(None, url, self.username, self.password) auth_handler = urllib2.HTTPBasicAuthHandler(password_mgr) opener = urllib2.build_opener(auth_handler) response = opener.open(url) else: response = urllib2.urlopen(request) except urllib2.URLError as e: raise GitanoCommandFailure( self.trovehost, ' '.join(gitano_args), str(e)) return response.read() class LocalTroveGitanoCommand(GitanoCommand): '''Run commands on the local Trove's Gitano. This is a version of the GitanoCommand class specifically for accessing the local Trove's Gitano. ''' def __init__(self): GitanoCommand.__init__(self, 'localhost', 'ssh', '', '') def new_gitano_command(statedb, trovehost): trove_info = statedb.get_trove_info(trovehost) return lorrycontroller.GitanoCommand( trovehost, trove_info['protocol'], trove_info['username'], trove_info['password'])