From 0d82cea312fd83db7c0bba204c17c60cb6b359a7 Mon Sep 17 00:00:00 2001 From: Seblu <sebastien.luttringer@smartjog.com> Date: Wed, 22 Dec 2010 13:39:48 +0100 Subject: [PATCH] Rome was not built in one day --- .gitignore | 2 + CHANGELOG | 2 + Makefile | 8 ++++ README | 7 +++ arch/PKGBUILD | 0 bin/cc-cli | 51 +++++++++++--------- cccli/__init__.py | 3 +- cccli/cli.py | 117 ++++++++++++++++++++++++++++++++++++---------- cccli/clierror.py | 3 ++ cccli/command.py | 54 +++++++++++++++++++++ cccli/parser.py | 1 + cccli/printer.py | 40 +++++++++------- debian/control | 4 +- 13 files changed, 226 insertions(+), 66 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 arch/PKGBUILD create mode 100644 cccli/command.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3d74a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*~ diff --git a/CHANGELOG b/CHANGELOG index e69de29..4c0d2bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -0,0 +1,2 @@ +v1 +- Initial release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ca3965b --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +## Makefile + +.PHONY: deb debclean + +all: + +deb: + dpkg-buildpackage diff --git a/README b/README index e69de29..245a349 100644 --- a/README +++ b/README @@ -0,0 +1,7 @@ +=========== +New release +=========== +Update version in debian/control +Update version in debian/changelog +Update version in setup.py +Update version in cccli/__init__.py diff --git a/arch/PKGBUILD b/arch/PKGBUILD new file mode 100644 index 0000000..e69de29 diff --git a/bin/cc-cli b/bin/cc-cli index 2d212cd..dbeb2aa 100755 --- a/bin/cc-cli +++ b/bin/cc-cli @@ -40,48 +40,55 @@ try: settings["debug"] = "True" # Parse line argument - oparser = optparse.OptionParser(usage="usage: %prog [options] [server[:port]]", + oparser = optparse.OptionParser(usage="usage: %prog [options] [commands]", version=cccli.version) oparser.add_option("-d", "--debug", action="store_true", dest="debug", - default=False, + default="", help="debug mode") oparser.add_option("-l", "--login", action="store", dest="login", default="", help="server login") + oparser.add_option("-s", "--server", + action="store", + dest="server", + default="", + help="server address") + oparser.add_option("-p", "--port", + action="store", + dest="port", + default="1984", + help="server port") + (options, args) = oparser.parse_args() + # options handling + for i in ("debug", "login", "server", "port"): + if hasattr(options, i): + o = getattr(options, i) + if o != "": + settings[i] = o + # show usage if no default conf and no server given - if len(settings) == 0 and len(args) == 0: + if "login" not in settings or "server" not in settings: oparser.print_help() sys.exit(1) - # debug option handling - if options.debug or "debug" in settings: - cccli.debug = True - - # args handling - if len(args) == 1: - sargs = args[0].split(":", 1) - if len(sargs) > 1: - settings["port"] = sargs[1] - if not sargs[0] == "": - settings["host"] = sargs[0] - - # remove password to prevent it showing + # remove pass to prevent stupid guy if "pass" in settings: del settings["pass"] # debug stuff - if cccli.debug: - printer.err("Debugging on") - printer.err("Settings: %s"%settings) - printer.err("Alias: %s"%alias) + if "debug" in settings: + cccli.debug = bool(settings["debug"]) else: warnings.filterwarnings("ignore") + printer.debug("Debugging on") + printer.debug("Settings: %s"%settings) + printer.debug("Alias: %s"%alias) # checking needs if settings["server"] == "": @@ -99,8 +106,8 @@ try: settings["pass"] = getpass.getpass("Password: ") # start cli - cli = cli.Cli(settings, alias) - cli.run() + cli = cli.Cli(settings, alias, args) + cli.start() except KeyboardInterrupt: exit(1) except Exception, e: diff --git a/cccli/__init__.py b/cccli/__init__.py index cb0cf1b..22fbde2 100644 --- a/cccli/__init__.py +++ b/cccli/__init__.py @@ -9,5 +9,6 @@ version = "1~dev" debug = False import printer -import parser +import cli import clierror +import command diff --git a/cccli/cli.py b/cccli/cli.py index 10f134d..b368dce 100644 --- a/cccli/cli.py +++ b/cccli/cli.py @@ -10,38 +10,107 @@ import sys import socket import ssl import threading +import cccli +from cccli import printer, command +from cccli.clierror import * from sjrpc.client import SimpleRpcClient from sjrpc.utils import RpcHandler, pure, threadless, ConnectionProxy +import re +import readline class Cli(object): - _settings = {} - _alias = {} - def __init__(self, settings, alias): + def __init__(self, settings, alias, args): self._settings = settings self._alias = alias + self._interactive = sys.stderr.isatty() and sys.stdin.isatty() + self._prompt = "> " + self._commands = args + self._rpc = None - def run(self): - # try to connect - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock = ssl.wrap_socket(sock, certfile=None, - cert_reqs=ssl.CERT_NONE, - ssl_version=ssl.PROTOCOL_TLSv1) - sock.connect((self._settings["server"], self._settings["port"])) - client = SimpleRpcClient(sock) - print "sucks" - prox = ConnectionProxy(client) - print "sucks1" - t = threading.Thread(target=client.run) - t.daemon = True - t.start() - print "sucks2" - prox.authentify(self._settings["login"], self._settings["pass"]) - print "sucks3" - # run parsing - #cparser = parser.Parser(main, alias) - #cparser.parse() + def start(self): + '''Start a CLI''' + # Connecting + self._connect() + # authentifications + self._auth() + # run parsing args + if len(self._commands) > 0: + for c in self._commands: + self._parse(c) + elif self._interactive: + self._interactive_parser() + else: + self._parser() + def _connect(self): + printer.debug("Connecting...") + rpcc = SimpleRpcClient.from_addr(self._settings["server"], + self._settings["port"], + enable_ssl=True + ) + # FIXME: wait sjrpc v5 with on_disconnect usable + rpcc.start(daemonize=True) + self._rpc = ConnectionProxy(rpcc) + def _auth(self): + printer.debug("Authenticating...") + if self._rpc.authentify(self._settings["login"], self._settings["pass"]): + printer.debug("Authenticated.") + else: + printer.fatal("Autentification failed!") + + def _interactive_parser(self): + '''Interactive shell parser''' + # init readline + + while True: + try: + line = raw_input(self._prompt) + self._parse_line(line) + except BadCommand, e: + printer.error("No such command: %s"%e[0]) + except EOFError: + printer.out("") + break + except SystemExit: + break + except KeyboardInterrupt: + printer.out("") + except Exception, e: + printer.error(e) + if cccli.debug: + raise + else: + printer.error("This is a not expected error, please report it!") + try: + pass + #readline.write_history_file(self._settings["histfile"]) + except IOError: + pass + printer.out("Tcho!") + + def _parser(self): + '''Non interactive parser''' + while True: + line = raw_input(self._prompt) + self._parse_line(line) + + 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] == "?"): + command.Command("help").call() + else: + argv = self._lex_argv(cmd) + command.Command(argv, self._rpc).call() + + def _lex_argv(self, string): + return string.split(" ") diff --git a/cccli/clierror.py b/cccli/clierror.py index 0f27d35..1ffd244 100644 --- a/cccli/clierror.py +++ b/cccli/clierror.py @@ -8,3 +8,6 @@ CloudControl CLI Errors ################################################################################ class cliError(Exception): pass + +class BadCommand(cliError): + pass diff --git a/cccli/command.py b/cccli/command.py new file mode 100644 index 0000000..fbf6656 --- /dev/null +++ b/cccli/command.py @@ -0,0 +1,54 @@ +import re +from sjrpc.client import SimpleRpcClient +from cccli import printer +from cccli.clierror import * + +class Command(object): + + def __init__(self, argv, rpc): + if len(argv) < 1: + raise Exception("Empty command") + self._argv = argv + self._rpc = rpc + + def call(self): + '''Run command''' + if re.match("^[a-zA-Z0-9]+$", self._argv[0]): + name = "cmd_%s"%self._argv[0] + if hasattr(self, name): + cmd = getattr(self, name) + return cmd(self._argv) + raise BadCommand(self._argv[0]) + + def usage(self, cmdname, printf=None): + '''Return usage of a command''' + fname = "cmd_%s"%cmdname + if hasattr(self, fname): + if hasattr(getattr(self, fname), "usage"): + s = getattr(getattr(self, fname), "usage") + if printf != None: + printf(s) + return s + else: + return "No usage" + else: + raise myError("No such command: %s"%cmdname) + + def cmd_exit(self, argv): + '''Quit application with respect''' + raise SystemExit() + + def cmd_list(self, argv): + '''List something''' + if len(argv) == 1: + argv.append("hv") + items = self._rpc.list(str.join("", argv[1:])) + for item in items: + for key, val in item.items(): + printer.out("%s: %s "%(key, val)) + printer.out("*"*80) + + def cmd_export(self, argv): + '''List server exported methods''' + for cmds in self._rpc.list_commands(): + printer.out("%s"%cmds["name"]) diff --git a/cccli/parser.py b/cccli/parser.py index d430c81..e461a30 100644 --- a/cccli/parser.py +++ b/cccli/parser.py @@ -31,6 +31,7 @@ class Parser(object): def _prompt(self): '''Show a parser prompt''' + return "> " bang = "#" if Keyring().has_unlocked_key() else "$" try: cwd = re.sub(self.rootpath, '~', os.getcwd()) diff --git a/cccli/printer.py b/cccli/printer.py index 88404a1..b35657b 100644 --- a/cccli/printer.py +++ b/cccli/printer.py @@ -6,6 +6,8 @@ CloudControl CLI Printers ''' import sys +import os +import cccli ############################################################################### color = { @@ -37,38 +39,42 @@ color = { } ############################################################################### -def out(message, fd=sys.stdout, nl="\n"): +def out(message, fd=sys.stdout, nl=os.linesep): '''Print a message in fd ended by nl''' - fd.write("%s%s"%(message,nl)) + fd.write("%s%s"%(message, nl)) fd.flush() ############################################################################### -def err(message, fd=sys.stderr, nl="\n"): +def err(message, fd=sys.stderr, nl=os.linesep): out(message, fd, nl) ############################################################################### -def fatal(message, exitcode=42, fd=sys.stderr, nl="\n"): - out("%sFatal Error:%s %s.%s"%(color["lred"], - color["red"], - message, - color["reset"]) - ,fd,nl) +def fatal(message, exitcode=42, fd=sys.stderr, nl=os.linesep): + out("%sFatal%s: %s%s"%(color["lred"], + color["red"], + message, + color["reset"]) + , fd, nl) if exitcode >= 0: - sys.exit(exitcode) + exit(exitcode) ############################################################################### -def error(message, fd=sys.stderr, nl="\n"): - out("%sError:%s %s.%s"%(color["lred"], - color["red"], - message, - color["reset"]) +def error(message, fd=sys.stderr, nl=os.linesep): + out("%sError%s: %s.%s"%(color["lred"], + color["red"], + message, + color["reset"]) ,fd,nl) ############################################################################### -def warn(message, fd=sys.stderr, nl="\n"): - out("%sWarning:%s %s.%s"%(color["lyellow"], +def warn(message, fd=sys.stderr, nl=os.linesep): + out("%sWarning%s: %s.%s"%(color["lyellow"], color["yellow"], message, color["reset"]) ,fd,nl) +############################################################################### +def debug(message, fd=sys.stderr, nl=os.linesep): + if cccli.debug: + out(message, fd, nl) diff --git a/debian/control b/debian/control index 53a6435..24c1a8f 100644 --- a/debian/control +++ b/debian/control @@ -3,13 +3,13 @@ Section: python Priority: optional Maintainer: Sebastien Luttringer <sebastien.luttringer@smartjog.com> Uploaders: Sebastien Luttringer <sebastien.luttringer@smartjog.com> -Build-Depends: debhelper (>= 7), python-central (>= 0.6), cdbs (>= 0.4.50), python-setuptools, python +Build-Depends: debhelper (>= 7), python-central (>= 0.6), cdbs (>= 0.4.50), python-setuptools, python (<< 3.0) XS-Python-Version: >= 2.6 Standards-Version: 3.8.0 Package: cc-cli Architecture: all -Depends: ${misc:Depends}, ${python:Depends} +Depends: ${misc:Depends}, ${python:Depends}, python (<< 3), python-sjrpc (>= 4) XB-Python-Version: ${python:Versions} Description: CloudControl CLI This package provides the Command Line Interface to CloudControl. -- GitLab