From 5b5b0e6bb68544947130511647d61136c73e4e14 Mon Sep 17 00:00:00 2001
From: Seblu <sebastien.luttringer@smartjog.com>
Date: Wed, 11 May 2011 15:58:41 +0200
Subject: [PATCH] Check remote functions in local command

Introduce new class RemoteCommand, which help checking needed remote functions are available
before adding a command to list of commands
---
 cccli/cli.py              | 11 +++---
 cccli/command/account.py  | 15 ++++++++
 cccli/command/cancel.py   |  9 +++--
 cccli/command/command.py  | 78 ++++++++++++++++++++++-----------------
 cccli/command/execute.py  |  3 ++
 cccli/command/jobs.py     |  3 ++
 cccli/command/kill.py     |  3 ++
 cccli/command/list.py     |  3 ++
 cccli/command/migrate.py  |  3 ++
 cccli/command/right.py    | 10 +++++
 cccli/command/server.py   |  9 +++--
 cccli/command/shutdown.py |  3 ++
 cccli/command/tag.py      |  9 +++++
 cccli/command/vm.py       | 21 +++++++++++
 cccli/commands.py         | 16 ++++++++
 15 files changed, 151 insertions(+), 45 deletions(-)

diff --git a/cccli/cli.py b/cccli/cli.py
index 418ff90..9dbf8ff 100644
--- a/cccli/cli.py
+++ b/cccli/cli.py
@@ -55,18 +55,19 @@ class Cli(object):
             self.printer.debug("Loaded history: %s"%len(self.printer.history))
             # enable completion
             self.printer.completion.set_completer(self._command_completer)
-        # load commands
+        # connecting
+        self._connect()
+        # auth
+        self._auth()
+        # load commands (need to be done after connection)
         self.commands = Commands(self)
+        self.printer.debug("Remote functions: %d"%len(self.commands.functions))
         self.printer.debug("Loaded commands: %d"%len(self.commands))
         # load alias
         self.aliases.load(self.settings.get("alias", None))
         self.printer.debug("Loaded aliases: %d"%len(self.aliases))
         # load tagdisplay
         self.tagdisplay.load(self.settings.get("tagdisplay", ""))
-        # connecting
-        self._connect()
-        # auth
-        self._auth()
         # parsing
         self._parse()
         # save history
diff --git a/cccli/command/account.py b/cccli/command/account.py
index 92bb425..350b846 100644
--- a/cccli/command/account.py
+++ b/cccli/command/account.py
@@ -40,6 +40,9 @@ class Command_addaccount(TqlCommand):
         if _pass is not None:
             self.rpccall("passwd", "a=%s"%self.args[0], _pass, _direct=True)
 
+    def remote_functions(self):
+        return set(("addaccount", "passwd"))
+
 
 class Command_delaccount(TqlCommand):
     '''Delete an account'''
@@ -54,6 +57,9 @@ class Command_delaccount(TqlCommand):
             raise cmdBadArgument("<tql>")
         self.rpccall("delaccount", self.args[0])
 
+    def remote_functions(self):
+        return set(("delaccount",))
+
 
 class Command_close(TqlCommand):
     '''Disable accounts'''
@@ -68,6 +74,9 @@ class Command_close(TqlCommand):
             raise cmdBadArgument("<tql>")
         self.rpccall("close", self.args[0])
 
+    def remote_functions(self):
+        return set(("close",))
+
 
 class Command_declose(TqlCommand):
     '''Enable accounts'''
@@ -82,6 +91,9 @@ class Command_declose(TqlCommand):
             raise cmdBadArgument("<tql>")
         self.rpccall("declose", self.args[0])
 
+    def remote_functions(self):
+        return set(("declose",))
+
 
 class Command_passwd(TqlCommand):
     '''Change account password'''
@@ -120,3 +132,6 @@ class Command_passwd(TqlCommand):
             raise cmdError("You don't type twice the same password. Aborted")
         # execute command
         self.rpccall("passwd", _tql, _pass)
+
+    def remote_functions(self):
+        return set(("passwd",))
diff --git a/cccli/command/cancel.py b/cccli/command/cancel.py
index 34ea57b..8442d6f 100644
--- a/cccli/command/cancel.py
+++ b/cccli/command/cancel.py
@@ -8,13 +8,13 @@ CloudControl jobs command
 from cccli.exception import *
 from sjrpc.core.exceptions import *
 from cccli.printer import Printer, color
-from cccli.command.command import OptionCommand
+from cccli.command.command import RemoteCommand
 
-class Command_cancel(OptionCommand):
+class Command_cancel(RemoteCommand):
     '''Cancel a job'''
 
     def __init__(self, cli, argv0):
-        OptionCommand.__init__(self, cli, argv0)
+        RemoteCommand.__init__(self, cli, argv0)
         self.set_usage("%prog [options] <job_id> ...")
 
     def __call__(self, argv):
@@ -32,3 +32,6 @@ class Command_cancel(OptionCommand):
                 self.printer.error("Invalid job id: %s"%jid)
             except RpcError as e:
                 self.printer.error("%s"%e)
+
+    def remote_functions(self):
+        return set(("cancel",))
diff --git a/cccli/command/command.py b/cccli/command/command.py
index 61bafac..4d098de 100644
--- a/cccli/command/command.py
+++ b/cccli/command/command.py
@@ -29,7 +29,7 @@ class Command(object):
 
 
 class OptionCommand(Command):
-    '''Add options parser to Command'''
+    '''Commands with options handling'''
 
     class OptionCommandParser(OptionParser):
         '''Parser of Option for OptionCommand'''
@@ -67,12 +67,53 @@ class OptionCommand(Command):
         '''Proxy to OptionParser'''
         self.optionparser.set_usage(*args, **kwargs)
 
-class TqlCommand(OptionCommand):
-    '''Add Tql stuff to Command'''
+class RemoteCommand(OptionCommand):
+    '''Command which needs connection to server'''
 
     def __init__(self, cli, argv0):
         OptionCommand.__init__(self, cli, argv0)
         self.rpc = cli.rpc
+
+    def remote_functions(self):
+        '''Return a set of needed remote functions'''
+        raise NotImplementedError
+
+    def print_objects(self, objectlist, ignore=None, index=False):
+        '''Trivial objectlist printing of tag'''
+        if objectlist is None:
+            return
+        _order = objectlist.get("order", None)
+        for (i,o) in enumerate(objectlist["objects"]):
+            if index:
+                self.printer.out("[%s] "%i, nl="")
+            self.print_tags(o, order=_order, ignore=ignore)
+
+    def print_tags(self, taglist, order=None, ignore=None):
+        '''Display a tag with tagdisplay settings'''
+        ignore = () if ignore is None else ignore
+        order = () if order is None else order
+        # copy dict to show
+        tl = taglist.copy()
+        # remove ignore tags
+        for tn in ignore:
+            tl.pop(tn, None)
+        # list to print
+        pls = []
+        # print firstly order tags
+        for tn in order:
+            tv = tl.pop(tn, None)
+            if tv is not None:
+                pls.append("%s%s:%s%s"%(self.tdtc(tn), tn, self.tdc(tn), self.tdr(tn, tv)))
+        # print tags without order, alpha ordered
+        for tn in sorted(tl.keys()):
+            pls.append("%s%s:%s%s"%(self.tdtc(tn), tn, self.tdc(tn), self.tdr(tn, tl[tn])))
+        self.printer.out("%s%s"%(" ".join(pls), color["reset"]))
+
+class TqlCommand(RemoteCommand):
+    '''Command which handle TQL stuff'''
+
+    def __init__(self, cli, argv0):
+        RemoteCommand.__init__(self, cli, argv0)
         self.set_usage("%prog [options] <tql>")
         # set tql filter stuff
         self.tql_filter = ""
@@ -214,34 +255,3 @@ class TqlCommand(OptionCommand):
                     self.print_objects(d, ["output"], index=False)
             except RpcError as e:
                 self.printer.error("RPCError: %s"%str(e))
-
-    def print_objects(self, objectlist, ignore=None, index=False):
-        '''Trivial objectlist printing of tag'''
-        if objectlist is None:
-            return
-        _order = objectlist.get("order", None)
-        for (i,o) in enumerate(objectlist["objects"]):
-            if index:
-                self.printer.out("[%s] "%i, nl="")
-            self.print_tags(o, order=_order, ignore=ignore)
-
-    def print_tags(self, taglist, order=None, ignore=None):
-        '''Display a tag with tagdisplay settings'''
-        ignore = () if ignore is None else ignore
-        order = () if order is None else order
-        # copy dict to show
-        tl = taglist.copy()
-        # remove ignore tags
-        for tn in ignore:
-            tl.pop(tn, None)
-        # list to print
-        pls = []
-        # print firstly order tags
-        for tn in order:
-            tv = tl.pop(tn, None)
-            if tv is not None:
-                pls.append("%s%s:%s%s"%(self.tdtc(tn), tn, self.tdc(tn), self.tdr(tn, tv)))
-        # print tags without order, alpha ordered
-        for tn in sorted(tl.keys()):
-            pls.append("%s%s:%s%s"%(self.tdtc(tn), tn, self.tdc(tn), self.tdr(tn, tl[tn])))
-        self.printer.out("%s%s"%(" ".join(pls), color["reset"]))
diff --git a/cccli/command/execute.py b/cccli/command/execute.py
index d66e78a..41dc467 100644
--- a/cccli/command/execute.py
+++ b/cccli/command/execute.py
@@ -30,3 +30,6 @@ class Command_execute(TqlCommand):
             self.printer.out("%sid:%s%s%s output:"%(self.tdtc("id"), self.tdc("id"),
                                                     o["id"], color["reset"]))
             self.printer.out(o.get("output", ""), nl="")
+
+    def remote_functions(self):
+        return set(("execute",))
diff --git a/cccli/command/jobs.py b/cccli/command/jobs.py
index 9162d18..570807e 100644
--- a/cccli/command/jobs.py
+++ b/cccli/command/jobs.py
@@ -38,3 +38,6 @@ class Command_jobs(TqlCommand):
                             show_done=self.options.done,
                             show_running=self.options.running)
         self.print_objects(objs, index=self.options.index)
+
+    def remote_functions(self):
+        return set(("jobs",))
diff --git a/cccli/command/kill.py b/cccli/command/kill.py
index 014cbe6..5ce3018 100644
--- a/cccli/command/kill.py
+++ b/cccli/command/kill.py
@@ -24,3 +24,6 @@ class Command_kill(TqlCommand):
             raise cmdBadArgument()
         # rpccall
         self.rpccall("kill", self.args[0])
+
+    def remote_functions(self):
+        return set(("kill",))
diff --git a/cccli/command/list.py b/cccli/command/list.py
index 0f33127..cce62e2 100644
--- a/cccli/command/list.py
+++ b/cccli/command/list.py
@@ -38,6 +38,9 @@ class Command_list(TqlCommand):
         else:
             self.print_objects(objs, index=self.options.index)
 
+    def remote_functions(self):
+        return set(("list",))
+
     def list_align(self, objs):
         '''Listing line aligned'''
         # get max size by tag
diff --git a/cccli/command/migrate.py b/cccli/command/migrate.py
index aba3c9f..967ccf2 100644
--- a/cccli/command/migrate.py
+++ b/cccli/command/migrate.py
@@ -69,6 +69,9 @@ class Command_migrate(TqlCommand):
         # run migration
         self.rpccall("migrate", scrutin, _direct=True)
 
+    def remote_functions(self):
+        return set(("migrate", "electiontypes", "election"))
+
     def get_electiontypes(self):
         '''Return a list of migration type'''
         try:
diff --git a/cccli/command/right.py b/cccli/command/right.py
index 0c997a2..c4fe42d 100644
--- a/cccli/command/right.py
+++ b/cccli/command/right.py
@@ -38,6 +38,9 @@ class Command_rights(TqlCommand):
                 tags = " ".join([ "%s%s:%s%s"%(self.tdtc(t), t, self.tdc(t), v) for (t,v) in r.items() ])
                 self.printer.out("[%s] %s%s"%(i,tags, color["reset"]))
 
+    def remote_functions(self):
+        return set(('rights',))
+
 
 class Command_addright(TqlCommand):
     '''Add or edit account right'''
@@ -67,6 +70,10 @@ class Command_addright(TqlCommand):
                     self.args[3],
                     self.args[4])
 
+    def remote_functions(self):
+        return set(('addright',))
+
+
 class Command_delright(TqlCommand):
     '''Delete account right'''
 
@@ -93,3 +100,6 @@ class Command_delright(TqlCommand):
         else:
             for index in l:
                 self.rpccall("delright", self.args[0], index)
+
+    def remote_functions(self):
+        return set(('delright',))
diff --git a/cccli/command/server.py b/cccli/command/server.py
index 222a51f..2252b66 100644
--- a/cccli/command/server.py
+++ b/cccli/command/server.py
@@ -7,13 +7,13 @@ CloudControl server command
 from cccli.exception import *
 from sjrpc.core.exceptions import *
 from cccli.printer import Printer, color
-from cccli.command.command import OptionCommand
+from cccli.command.command import RemoteCommand
 
-class Command_server(OptionCommand):
+class Command_server(RemoteCommand):
     '''Server manipulation command'''
 
     def __init__(self, cli, argv0):
-        OptionCommand.__init__(self, cli, argv0)
+        RemoteCommand.__init__(self, cli, argv0)
         self.set_usage("%prog <options>")
         self.add_option("-c", action="store_true", dest="cache",
                         help="show server cache")
@@ -35,6 +35,9 @@ class Command_server(OptionCommand):
         else:
             self.printer.out(self.usage())
 
+    def remote_functions(self):
+        return set(('dbstats', 'version'))
+
     def show_functions(self):
         try:
             for cmds in self.cli.rpc.call("functions"):
diff --git a/cccli/command/shutdown.py b/cccli/command/shutdown.py
index 508e428..c80c6de 100644
--- a/cccli/command/shutdown.py
+++ b/cccli/command/shutdown.py
@@ -30,3 +30,6 @@ class Command_shutdown(TqlCommand):
             raise cmdBadArgument()
         self.rpccall("shutdown", self.args[0], self.options.reboot,
                      self.options.graceful)
+
+    def remote_functions(self):
+        return set(("shutdown",))
diff --git a/cccli/command/tag.py b/cccli/command/tag.py
index 2ae7284..ba10e1e 100644
--- a/cccli/command/tag.py
+++ b/cccli/command/tag.py
@@ -35,6 +35,9 @@ class Command_tags(TqlCommand):
         # display answer
         self.print_objects(objs)
 
+    def remote_functions(self):
+        return set(("tags",))
+
 
 class Command_addtag(TqlCommand):
     '''Add/Modify a static tag on an account'''
@@ -52,6 +55,9 @@ class Command_addtag(TqlCommand):
         # rpc call
         self.rpccall("addtag", self.args[0], self.args[1], self.args[2])
 
+    def remote_functions(self):
+        return set(("addtag",))
+
 
 class Command_deltag(TqlCommand):
     '''Delete a static tag from an account'''
@@ -68,3 +74,6 @@ class Command_deltag(TqlCommand):
             raise cmdBadArgument()
         # rpc call
         self.rpccall("deltag", self.args[0], self.args[1])
+
+    def remote_functions(self):
+        return set(("deltag",))
diff --git a/cccli/command/vm.py b/cccli/command/vm.py
index d9d44c4..6892051 100644
--- a/cccli/command/vm.py
+++ b/cccli/command/vm.py
@@ -23,6 +23,9 @@ class Command_start(TqlCommand):
         # rpc call
         self.rpccall("start", self.args[0])
 
+    def remote_functions(self):
+        return set(("start",))
+
 
 class Command_stop(TqlCommand):
     '''Stop a running vm'''
@@ -39,6 +42,9 @@ class Command_stop(TqlCommand):
         # rpc call
         self.rpccall("stop", self.args[0])
 
+    def remote_functions(self):
+        return set(("stop",))
+
 
 class Command_destroy(TqlCommand):
     '''Force a vm to stop'''
@@ -55,6 +61,9 @@ class Command_destroy(TqlCommand):
         # rpc call
         self.rpccall("destroy", self.args[0])
 
+    def remote_functions(self):
+        return set(("destroy",))
+
 
 class Command_pause(TqlCommand):
     '''Pause a running vm'''
@@ -71,6 +80,9 @@ class Command_pause(TqlCommand):
         # rpc call
         self.rpccall("pause", self.args[0])
 
+    def remote_functions(self):
+        return set(("pause",))
+
 
 class Command_resume(TqlCommand):
     '''Resume a paused vm'''
@@ -87,6 +99,9 @@ class Command_resume(TqlCommand):
         # rpc call
         self.rpccall("resume", self.args[0])
 
+    def remote_functions(self):
+        return set(("resume",))
+
 class Command_undefine(TqlCommand):
     '''Undefine a stopped vm'''
 
@@ -104,6 +119,9 @@ class Command_undefine(TqlCommand):
         # rpc call
         self.rpccall("undefine", self.args[0], self.options.clean)
 
+    def remote_functions(self):
+        return set(("undefine",))
+
 class Command_clone(TqlCommand):
     '''Clone vm'''
 
@@ -149,3 +167,6 @@ class Command_clone(TqlCommand):
             # run migration
             self.tql_filter = ""
             self.rpccall("clone", stql, dtql, newname, _direct=True)
+
+    def remote_functions(self):
+        return set(("clone",))
diff --git a/cccli/commands.py b/cccli/commands.py
index 39c37eb..56631ad 100644
--- a/cccli/commands.py
+++ b/cccli/commands.py
@@ -21,6 +21,22 @@ class Commands(object):
         self.cmds = dict()
         for x in [ x for x in globals() if x.startswith("Command_") ]:
             self.cmds[x[8:]] = globals()[x]
+        # build remote function list
+        try:
+            self.functions = set([ c["name"]  for c in self.cli.rpc.call("functions") ])
+        except RpcError as e:
+            raise cliError("RPCError: Unable to retrieve remote commands: %s"%str(e))
+        # remove not available commands
+        for cname in tuple(self.cmds):
+            cobj = self.cmds[cname](self.cli, cname)
+            if isinstance(cobj, command.RemoteCommand):
+                try:
+                    if not cobj.remote_functions().issubset(self.functions):
+                        del self.cmds[cname]
+                        self.cli.printer.debug("Command %s not available"%cname)
+                except NotImplementedError as e:
+                    self.cli.printer.debug("Command %s lack of remote_functions"%cname)
+                    del self.cmds[cname]
 
     def __len__(self):
         return len(self.cmds)
-- 
GitLab