Commit 5d3a9380 authored by Thomas Souvignet's avatar Thomas Souvignet Committed by Sébastien Luttringer
Browse files

implement 'rshell' command

parent 3fad46e5
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -9,6 +9,10 @@ import ConfigParser
import os
import shlex
import imp
import sys
import termios
import threading
import tty

from cccli.exception import *
from sjrpc.core import RpcError
@@ -410,3 +414,46 @@ class TqlCommand(RemoteCommand):
            args2[self.options.tql_index] = "id=%s"%obj["id"]
            # now we can make call
            self._unsecure_rpccall(args2, **kwargs)

class ConsoleCommand(TqlCommand):

    def __init__(self, cli, argv0):
        TqlCommand.__init__(self, cli, argv0)

    def start_console(self, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr):
        self.stdin = stdin.fileno()
        self.stdout = stdout.fileno()
        self.stderr = stderr.fileno()
        if self.stdin == sys.stdin.fileno():
            sys.stdin.flush()
        if self.stdout == sys.stdout.fileno():
            sys.stdout.flush()
        if self.stderr == sys.stderr.fileno():
            sys.stderr.flush()
        self._old_settings = termios.tcgetattr(self.stdout)
        tty.setraw(self.stdin)
        self.running = True

    def stop_console(self):
        # Reset terminal attributes.
        termios.tcsetattr(sys.stdout, termios.TCSADRAIN, self._old_settings)
        self.running = False

    def recv(self, size):
        return os.read(self.stdin, 1024)

    def send(self, data):
        return os.write(self.stdout, data)

    def fileno(self):
        return self.stdin

    def setblocking(self, blocking):
        # Do NOT be in non-blocking mode : non-blocking mode
        # causes rshell to get EAGAIN errors when having to
        # output loads of data, causing the TTY to break.
        return

    def close(self):
        # Reset terminal attributes.
        termios.tcsetattr(sys.stdout, termios.TCSADRAIN, self._old_settings)
+95 −0
Original line number Diff line number Diff line
#!/usr/bin/env python
#coding=utf8

'''
Connection to a remote shell
'''

import os
import sys
import tty
import fcntl
import signal
import struct
import termios

from cccli.exception import *
from sjrpc.core.exceptions import *
from cccli.printer import Printer, color
from cccli.command import ConsoleCommand

from sjrpc.core.protocols import TunnelProtocol
from sjrpc.core import AsyncWatcher

class Command_rshell(ConsoleCommand):
    '''Start a remote shell on the provided host'''

    def __init__(self, cli, argv0):
        ConsoleCommand.__init__(self, cli, argv0)
        self.tql_filter = "&con"

    def __call__(self, argv):
        # args parse
        self.parse_args(argv)
        if len(self.args) != 1:
            raise cmdBadArgument()
        # rpccall
        self.rpccall("rshell", self.args[0], _callback=self._cb_open_shell,
                     _status=True, _direct=True)

    def _cb_open_shell(self, response):
        tun = None
        if not 'objects' in response:
            return
        response = response['objects'][0]
        if not 'status' in response:
            return
        if response['status'] == 'success':
            label = response['output']
            try:
                # Create terminal
                self.start_console(sys.stdin, sys.stdout, sys.stderr)
                # Connect to remote end using sjrpc
                tun = self.rpc.create_tunnel(endpoint=self)
                # Terminal resizing event handler
                def sig_resize(signum, frame):
                    size = struct.pack('HHHH', 0, 0, 0, 0)
                    size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size)
                    size = struct.unpack('HHHH', size)
                    self.rpc.call('rshell_resize', label, *size)
                signal.signal(signal.SIGWINCH, sig_resize)
                # Init size for term
                size = struct.pack('HHHH', 0, 0, 0, 0)
                size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size)
                size = struct.unpack('HHHH', size)
                self.rpc.call('rshell_resize', label, *size)
                # Setup and run event loop
                watcher = AsyncWatcher()
                watcher.register(self.rpc.rpc, 'rshell_wait', label)
                while self.running:
                    returned = watcher.wait(1)
                    if returned:
                        if 'return' in returned[0]:
                            rcode = returned[0]['return']
                            break
                        elif 'error' in returned[0]:
                            raise RpcError(Exception(returned[0]['error']['message']),
                                           Exception(returned[0]['error']['message']))
                # Clear signal handlers
                signal.signal(signal.SIGWINCH, signal.SIG_IGN)
            except Exception:
                # Cleanup
                self.stop_console()
                if tun:
                    tun.shutdown()
                    tun.close()
                raise
            else:
                # Cleanup
                self.stop_console()
                if tun:
                    tun.shutdown()
                    tun.close()

    def remote_functions(self):
        return set(("rshell",))