Skip to content
Snippets Groups Projects
cli.py 6.47 KiB
Newer Older
#!/usr/bin/env python
#coding=utf8

'''
CloudControl CLI class
'''

import os, os.path
import sys
import socket
import ssl
import threading
Seblu's avatar
Seblu committed
import subprocess
import ConfigParser
Seblu's avatar
Seblu committed
import re

from cccli import printer, command, version, debug
Seblu's avatar
Seblu committed
from cccli.command import Command
Seblu's avatar
Seblu committed
from cccli.clierror import *
from sjrpc.core.exceptions import *
from sjrpc.client import SimpleRpcClient
from sjrpc.utils import RpcHandler, ConnectionProxy, pure
Seblu's avatar
Seblu committed
    def __init__(self, settings):
        self.isinteractive = sys.stderr.isatty() and sys.stdin.isatty()
        self.settings = settings
        self.prompt = "> "
Seblu's avatar
Seblu committed
        self.rpc = None
Seblu's avatar
Seblu committed
        self.alias = Alias()
        self.history = History()
Seblu's avatar
Seblu committed
    def start(self, line=""):
Seblu's avatar
Seblu committed
        '''Start a CLI'''
Seblu's avatar
Seblu committed
        # not interactive is command line
        if line:
            self.isinteractive = False
        # start readline and load history
        if self.isinteractive:
            import readline
            self.history.readline = readline
            self.history.load(self.settings.get("history", ""))
            self.history.maxsize(self.settings.get("hsize", None))
        # load alias
        self.alias.load(self.settings.get("alias", ""))
        printer.debug("Alias: %s"%self.alias)
Seblu's avatar
Seblu committed
        # Connecting
        self._connect()
        # authentifications
        self._auth()
        # run parsing args
Seblu's avatar
Seblu committed
        if line:
            self._parse_line(line)
Seblu's avatar
Seblu committed
        else:
Seblu's avatar
Seblu committed
            self._shell()
        self.history.save(self.settings.get("history", ""))

Seblu's avatar
Seblu committed
    def _connect(self):
        printer.debug("Connecting...")
Seblu's avatar
Seblu committed
        rpcc = SimpleRpcClient.from_addr(self.settings["server"],
                                         self.settings["port"],
Seblu's avatar
Seblu committed
                                         enable_ssl=True,
                                         default_handler=CliHandler(),
Seblu's avatar
Seblu committed
                                         timeout=self.settings["timeout"]
Seblu's avatar
Seblu committed
                                        )
        rpcc.start(daemonize=True)
Seblu's avatar
Seblu committed
        self.rpc = ConnectionProxy(rpcc)

Seblu's avatar
Seblu committed
    def _auth(self):
        printer.debug("Authenticating...")
Seblu's avatar
Seblu committed
        if self.rpc.authentify(self.settings["login"], self.settings["pass"]):
Seblu's avatar
Seblu committed
            printer.debug("Authenticated.")
        else:
            printer.fatal("Autentification failed!")

Seblu's avatar
Seblu committed
    def _shell(self):
        '''Shell parser'''
Seblu's avatar
Seblu committed
        while True:
            try:
Seblu's avatar
Seblu committed
                line = raw_input(self.prompt)
Seblu's avatar
Seblu committed
                self._parse_line(line)
            except EOFError:
                printer.out("")
                break
            except SystemExit:
                break
            except KeyboardInterrupt:
                printer.out("")
        printer.out("Tcho!")

    def _parse_line(self, line):
        '''Parse a line (more than one command)'''
        for cmd in line.split(";"):
            if (cmd.strip() == "" or cmd[0] == "#"):
                continue
            elif (cmd[0] == "!"):
                p = subprocess.Popen(cmd[1:], close_fds=True, shell=True)
                p.wait()
                ret = p.returncode
            elif (cmd[0] == "?"):
Seblu's avatar
Seblu committed
                Command(["help"], self).call()
Seblu's avatar
Seblu committed
            else:
                self._parse_command(cmd)

    def _parse_command(self, cmd):
        try:
            # lex command
            argv = self._lex_argv(cmd)
            # alias subs
            if argv[0] in self.alias:
                argv[0] = self.alias[argv[0]]
            # execute command
            Command(argv, self).call()
        except BadArgument, e:
Seblu's avatar
Seblu committed
            if str(e):
Seblu's avatar
Seblu committed
                printer.error("Bad argument: %s."%str(e))
Seblu's avatar
Seblu committed
            else:
Seblu's avatar
Seblu committed
                printer.error("Bad argument.")
            usage = Command.usage(argv[0])
            if usage != "":
                printer.out("usage: %s."%usage)
Seblu's avatar
Seblu committed
        except BadCommand, e:
            if str(e):
                printer.error("command: %s."%str(e))
            else:
                printer.error("No command: %s."%argv[0])
Seblu's avatar
Seblu committed
        except RpcError, e:
            if debug: raise
Seblu's avatar
Seblu committed
            printer.error("sjRPC: %s"%str(e))
        except Exception, e:
Seblu's avatar
Seblu committed
            if debug: raise
Seblu's avatar
Seblu committed
            printer.error("%s: %s."%(argv[0], str(e)))
Seblu's avatar
Seblu committed

    def _lex_argv(self, string):
Seblu's avatar
Seblu committed
        '''Lex command argument'''
Seblu's avatar
Seblu committed
        return string.split(" ")
Seblu's avatar
Seblu committed

class Alias(dict):
    ''' Alias wrapper'''
Seblu's avatar
Seblu committed
    def load(self, filename):
Seblu's avatar
Seblu committed
        '''load alias from file'''
Seblu's avatar
Seblu committed
        if os.access(filename, os.R_OK):
Seblu's avatar
Seblu committed
            fparser = ConfigParser.SafeConfigParser()
Seblu's avatar
Seblu committed
            fparser.read(filename)
Seblu's avatar
Seblu committed
            if fparser.has_section("alias"):
                self.clear()
                self.update(fparser.items("alias"))

Seblu's avatar
Seblu committed
    def save(self, filename):
Seblu's avatar
Seblu committed
        '''save alias on file'''
Seblu's avatar
Seblu committed
        if os.access(filename, os.R_OK or os.W_OK):
Seblu's avatar
Seblu committed
            fparser = ConfigParser.SafeConfigParser()
Seblu's avatar
Seblu committed
            fparser.read(filename)
Seblu's avatar
Seblu committed
            fparser.remove_section("alias")
            fparser.add_section("alias")
            for n,v in self.items():
                fparser.set("alias", n, v)
Seblu's avatar
Seblu committed
            fparser.write(open(filename, "w"))


class History(object):
    '''History class'''
    def __init__(self, readline=None):
        self.readline = readline

    def __nonzero__(self):
        return not self.readline is None

    def __getattribute__(self, name):
        r = object.__getattribute__(self, "readline")
        if name == "readline":
            return r
        if r is None:
            return lambda *a,**k: None
        return object.__getattribute__(self, name)

    def __iter__(self):
        for i in range(1, len(self)):
            yield self.readline.get_history_item(i)

    def __len__(self):
        return self.readline.get_current_history_length()

    def load(self, path):
        '''Load history from a file'''
        try:
            self.readline.read_history_file(path)
        except IOError:
            pass

    def save(self, path):
        '''Save history into path'''
        try:
            self.readline.write_history_file(path)
        except IOError:
            pass

    def maxsize(self, size=None):
        '''Set or return max history size'''
        if size is not None:
            self.readline.set_history_length(size)
        return self.readline.get_history_length()


class CliHandler(RpcHandler):
    '''Handle RPC incoming request'''

    @pure
    def get_tags(self, tags=()):
        if "version" in tags:
Seblu's avatar
Seblu committed
            return { "version": version }

    @pure
    def quit(self, rpc=None):
        printer.fatal("Disconnected from server!")