Index: third_party/gsutil/boto/boto/manage/cmdshell.py |
diff --git a/third_party/gsutil/boto/boto/manage/cmdshell.py b/third_party/gsutil/boto/boto/manage/cmdshell.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9ee013361cc3a15b75ad0543408fccaedd680319 |
--- /dev/null |
+++ b/third_party/gsutil/boto/boto/manage/cmdshell.py |
@@ -0,0 +1,241 @@ |
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ |
+# |
+# Permission is hereby granted, free of charge, to any person obtaining a |
+# copy of this software and associated documentation files (the |
+# "Software"), to deal in the Software without restriction, including |
+# without limitation the rights to use, copy, modify, merge, publish, dis- |
+# tribute, sublicense, and/or sell copies of the Software, and to permit |
+# persons to whom the Software is furnished to do so, subject to the fol- |
+# lowing conditions: |
+# |
+# The above copyright notice and this permission notice shall be included |
+# in all copies or substantial portions of the Software. |
+# |
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
+# IN THE SOFTWARE. |
+ |
+from boto.mashups.interactive import interactive_shell |
+import boto |
+import os |
+import time |
+import shutil |
+import StringIO |
+import paramiko |
+import socket |
+import subprocess |
+ |
+ |
+class SSHClient(object): |
+ |
+ def __init__(self, server, |
+ host_key_file='~/.ssh/known_hosts', |
+ uname='root', ssh_pwd=None): |
+ self.server = server |
+ self.host_key_file = host_key_file |
+ self.uname = uname |
+ self._pkey = paramiko.RSAKey.from_private_key_file(server.ssh_key_file, |
+ password=ssh_pwd) |
+ self._ssh_client = paramiko.SSHClient() |
+ self._ssh_client.load_system_host_keys() |
+ self._ssh_client.load_host_keys(os.path.expanduser(host_key_file)) |
+ self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
+ self.connect() |
+ |
+ def connect(self, num_retries=5): |
+ retry = 0 |
+ while retry < num_retries: |
+ try: |
+ self._ssh_client.connect(self.server.hostname, |
+ username=self.uname, |
+ pkey=self._pkey) |
+ return |
+ except socket.error, (value, message): |
+ if value in (51, 61, 111): |
+ print 'SSH Connection refused, will retry in 5 seconds' |
+ time.sleep(5) |
+ retry += 1 |
+ else: |
+ raise |
+ except paramiko.BadHostKeyException: |
+ print "%s has an entry in ~/.ssh/known_hosts and it doesn't match" % self.server.hostname |
+ print 'Edit that file to remove the entry and then hit return to try again' |
+ raw_input('Hit Enter when ready') |
+ retry += 1 |
+ except EOFError: |
+ print 'Unexpected Error from SSH Connection, retry in 5 seconds' |
+ time.sleep(5) |
+ retry += 1 |
+ print 'Could not establish SSH connection' |
+ |
+ def open_sftp(self): |
+ return self._ssh_client.open_sftp() |
+ |
+ def get_file(self, src, dst): |
+ sftp_client = self.open_sftp() |
+ sftp_client.get(src, dst) |
+ |
+ def put_file(self, src, dst): |
+ sftp_client = self.open_sftp() |
+ sftp_client.put(src, dst) |
+ |
+ def open(self, filename, mode='r', bufsize=-1): |
+ """ |
+ Open a file on the remote system and return a file-like object. |
+ """ |
+ sftp_client = self.open_sftp() |
+ return sftp_client.open(filename, mode, bufsize) |
+ |
+ def listdir(self, path): |
+ sftp_client = self.open_sftp() |
+ return sftp_client.listdir(path) |
+ |
+ def isdir(self, path): |
+ status = self.run('[ -d %s ] || echo "FALSE"' % path) |
+ if status[1].startswith('FALSE'): |
+ return 0 |
+ return 1 |
+ |
+ def exists(self, path): |
+ status = self.run('[ -a %s ] || echo "FALSE"' % path) |
+ if status[1].startswith('FALSE'): |
+ return 0 |
+ return 1 |
+ |
+ def shell(self): |
+ """ |
+ Start an interactive shell session on the remote host. |
+ """ |
+ channel = self._ssh_client.invoke_shell() |
+ interactive_shell(channel) |
+ |
+ def run(self, command): |
+ """ |
+ Execute a command on the remote host. Return a tuple containing |
+ an integer status and a two strings, the first containing stdout |
+ and the second containing stderr from the command. |
+ """ |
+ boto.log.debug('running:%s on %s' % (command, self.server.instance_id)) |
+ status = 0 |
+ try: |
+ t = self._ssh_client.exec_command(command) |
+ except paramiko.SSHException: |
+ status = 1 |
+ std_out = t[1].read() |
+ std_err = t[2].read() |
+ t[0].close() |
+ t[1].close() |
+ t[2].close() |
+ boto.log.debug('stdout: %s' % std_out) |
+ boto.log.debug('stderr: %s' % std_err) |
+ return (status, std_out, std_err) |
+ |
+ def run_pty(self, command): |
+ """ |
+ Execute a command on the remote host with a pseudo-terminal. |
+ Returns a string containing the output of the command. |
+ """ |
+ boto.log.debug('running:%s on %s' % (command, self.server.instance_id)) |
+ channel = self._ssh_client.get_transport().open_session() |
+ channel.get_pty() |
+ channel.exec_command(command) |
+ return channel |
+ |
+ def close(self): |
+ transport = self._ssh_client.get_transport() |
+ transport.close() |
+ self.server.reset_cmdshell() |
+ |
+class LocalClient(object): |
+ |
+ def __init__(self, server, host_key_file=None, uname='root'): |
+ self.server = server |
+ self.host_key_file = host_key_file |
+ self.uname = uname |
+ |
+ def get_file(self, src, dst): |
+ shutil.copyfile(src, dst) |
+ |
+ def put_file(self, src, dst): |
+ shutil.copyfile(src, dst) |
+ |
+ def listdir(self, path): |
+ return os.listdir(path) |
+ |
+ def isdir(self, path): |
+ return os.path.isdir(path) |
+ |
+ def exists(self, path): |
+ return os.path.exists(path) |
+ |
+ def shell(self): |
+ raise NotImplementedError('shell not supported with LocalClient') |
+ |
+ def run(self): |
+ boto.log.info('running:%s' % self.command) |
+ log_fp = StringIO.StringIO() |
+ process = subprocess.Popen(self.command, shell=True, stdin=subprocess.PIPE, |
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
+ while process.poll() == None: |
+ time.sleep(1) |
+ t = process.communicate() |
+ log_fp.write(t[0]) |
+ log_fp.write(t[1]) |
+ boto.log.info(log_fp.getvalue()) |
+ boto.log.info('output: %s' % log_fp.getvalue()) |
+ return (process.returncode, log_fp.getvalue()) |
+ |
+ def close(self): |
+ pass |
+ |
+class FakeServer(object): |
+ """ |
+ A little class to fake out SSHClient (which is expecting a |
+ :class`boto.manage.server.Server` instance. This allows us |
+ to |
+ """ |
+ def __init__(self, instance, ssh_key_file): |
+ self.instance = instance |
+ self.ssh_key_file = ssh_key_file |
+ self.hostname = instance.dns_name |
+ self.instance_id = self.instance.id |
+ |
+def start(server): |
+ instance_id = boto.config.get('Instance', 'instance-id', None) |
+ if instance_id == server.instance_id: |
+ return LocalClient(server) |
+ else: |
+ return SSHClient(server) |
+ |
+def sshclient_from_instance(instance, ssh_key_file, |
+ host_key_file='~/.ssh/known_hosts', |
+ user_name='root', ssh_pwd=None): |
+ """ |
+ Create and return an SSHClient object given an |
+ instance object. |
+ |
+ :type instance: :class`boto.ec2.instance.Instance` object |
+ :param instance: The instance object. |
+ |
+ :type ssh_key_file: str |
+ :param ssh_key_file: A path to the private key file used |
+ to log into instance. |
+ |
+ :type host_key_file: str |
+ :param host_key_file: A path to the known_hosts file used |
+ by the SSH client. |
+ Defaults to ~/.ssh/known_hosts |
+ :type user_name: str |
+ :param user_name: The username to use when logging into |
+ the instance. Defaults to root. |
+ |
+ :type ssh_pwd: str |
+ :param ssh_pwd: The passphrase, if any, associated with |
+ private key. |
+ """ |
+ s = FakeServer(instance, ssh_key_file) |
+ return SSHClient(s, host_key_file, user_name, ssh_pwd) |