Giuseppe Lavagetto has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/215891

Change subject: conftool: adding the cli-tool
......................................................................

conftool: adding the cli-tool

Change-Id: I87147db34746d9f9b02cc93d97fecaec69aa6912
---
M conftool/__init__.py
A conftool/action.py
M conftool/cli/syncer.py
A conftool/cli/tool.py
M conftool/node.py
5 files changed, 154 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/software/conftool 
refs/changes/91/215891/1

diff --git a/conftool/__init__.py b/conftool/__init__.py
index 1ba4c8a..36aea6d 100644
--- a/conftool/__init__.py
+++ b/conftool/__init__.py
@@ -1,5 +1,6 @@
 import os
 import logging
+import json
 from conftool import backend
 from conftool import drivers
 
@@ -22,6 +23,10 @@
     def key(self):
         raise NotImplementedError("All kvstore objects should implement this")
 
+    @property
+    def name(self):
+        return os.path.basename(self.key)
+
     def get_default(self, what):
         raise NotImplementedError("All kvstore objects should implement this.")
 
@@ -43,6 +48,23 @@
     def delete(self):
         self.backend.driver.delete(self.key)
 
+    @classmethod
+    def get_tags(cls, taglist):
+        tuplestrip = lambda tup: tuple(map(lambda x: x.strip(), tup))
+        tagdict = dict([tuplestrip(el.split('=')) for el in taglist])
+        # will raise a KeyError if not all tags are matched
+        return [tagdict[t] for t in cls._tags]
+
+    def update(self, values):
+        """
+        Update values of properties in the schema
+        """
+        for k, v in values.items():
+            if k not in self._schema:
+                continue
+            self._set_value(k, self._schema[k], {k: v}, set_defaults=False)
+        self.write()
+
     def _from_net(self, values):
         """
         Fetch the values from the kvstore into the object
@@ -59,9 +81,14 @@
                 values[key] = self.get_default(key)
         return values
 
-    def _set_value(self, key, validator, values):
+    def _set_value(self, key, validator, values, set_defaults=True):
         try:
             setattr(self, key, validator(values[key]))
         except Exception as e:
             # TODO: log validation error
-            setattr(self, key, self.get_default(key))
+            if set_defaults:
+                setattr(self, key, self.get_default(key))
+
+    def __str__(self):
+        d = {self.name: self._to_net()}
+        return json.dumps(d)
diff --git a/conftool/action.py b/conftool/action.py
new file mode 100644
index 0000000..2f73aa8
--- /dev/null
+++ b/conftool/action.py
@@ -0,0 +1,50 @@
+config = {}
+backend = None
+
+
+class ActionError(Exception):
+    pass
+
+
+class Action(object):
+
+    def __init__(self, obj, act):
+        self.action, self.args = self._parse_action(act)
+        self.entity = obj
+        self.description = ""
+
+    def _parse_action(self, act):
+        if act.startswith('get'):
+            return ('get', None)
+        elif act.startswith('delete'):
+            return ('delete', None)
+        elif not act.startswith('set/'):
+            raise ActionError("Cannot parse action %s" % act)
+        set_arg = act.strip('set/')
+        try:
+            values = dict((el.strip().split('=')) for el in set_arg.split(':'))
+        except Exception as e:
+            raise ActionError("Could not parse set instructions: %s" % set_arg)
+        return ('set', values)
+
+    def run(self):
+        if self.action == 'get':
+            self.entity.fetch()
+            if self.entity.exists:
+                return str(self.entity)
+            else:
+                return "%s not found" % self.entity.name
+        elif self.action == 'delete':
+            self.entity.delete()
+            entity_type = self.entity.__class__.__name__,
+            return "Deleted %s %s." % (entity_type,
+                                       self.entity.name)
+        else:
+            desc = []
+            for (k, v) in self.args.items():
+                msg = "%s: %s changed %s => %s" % (
+                    self.entity.name, k,
+                    getattr(self.entity, k), v)
+                desc.append(msg)
+            self.entity.update(self.args)
+            return "\n".join(desc)
diff --git a/conftool/cli/syncer.py b/conftool/cli/syncer.py
index 73c797d..6f030ab 100644
--- a/conftool/cli/syncer.py
+++ b/conftool/cli/syncer.py
@@ -10,6 +10,7 @@
 import functools
 import logging
 
+
 # Generic exception handling decorator
 def catch_and_log(log_msg):
     def actual_wrapper(fn):
diff --git a/conftool/cli/tool.py b/conftool/cli/tool.py
new file mode 100644
index 0000000..8a2a9d9
--- /dev/null
+++ b/conftool/cli/tool.py
@@ -0,0 +1,73 @@
+# Conftool cli module
+#
+import argparse
+import sys
+import logging
+import json
+from conftool import configuration, action, _log, KVObject
+from conftool.drivers import BackendError
+# TODO: auto import these somehow
+from conftool import service, node
+
+object_types = {"node": node.Node, "service": service.Service}
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Tool to interact with the WMF config store",
+        epilog="More details at"
+        " <https://wikitech.wikimedia.org/wiki/conftool>.",
+        fromfile_prefix_chars='@')
+    parser.add_argument('--config', nargs=1, help="Optional config file",
+                        default="/etc/conftool/config")
+    parser.add_argument('--tags',
+                        help="List of comma-separated tags; they need to "
+                        "match the base tags of the object type you chose.",
+                        required=True)
+    parser.add_argument('--object-type', dest="object_type",
+                        choices=object_types.keys(), default='node')
+    parser.add_argument('--action', action="append", metavar="ACTIONS",
+                        help="the action to take: "
+                        " [set/k1=v1:k2=v2...|get|delete] node", nargs=2,
+                        required=True)
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.WARN)
+    try:
+        c = configuration.get(args.config)
+        KVObject.setup(c)
+    except Exception as e:
+        _log.critical("Invalid configuration: %s", e)
+        sys.exit(1)
+
+    cls = object_types[args.object_type]
+    try:
+        tags = cls.get_tags(args.tags.split(','))
+    except KeyError as e:
+        _log.critical("Invalid tag list %s - reason: %s", args.tags, e)
+        sys.exit(1)
+
+    for unit in args.action:
+        try:
+            act, name = unit
+            if act == 'get' and name == "all":
+                cur_dir = cls.dir(*tags)
+                print json.dumps(dict(KVObject.backend.driver.ls(cur_dir)))
+                return
+            # Oh python I <3 you...
+            arguments = list(tags)
+            arguments.append(name)
+            obj = cls(*arguments)
+            a = action.Action(obj, act)
+            msg = a.run()
+        except action.ActionError as e:
+            _log.error("Invalid action, reason: %s", str(e))
+        except BackendError as e:
+            _log.error("Failure writing to the kvstore: %s", str(e))
+        except Exception as e:
+            raise
+            _log.error("Generic action failure: %s", str(e))
+        else:
+            print(msg)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/conftool/node.py b/conftool/node.py
index 9262a84..4dfedd9 100644
--- a/conftool/node.py
+++ b/conftool/node.py
@@ -13,6 +13,7 @@
 class Node(KVObject):
 
     _schema = {'weight': int, 'pooled': choice("yes", "no", "inactive")}
+    _tags = ['dc', 'cluster', 'service']
 
     def __init__(self, datacenter, cluster, servname, host):
         self.base_path = self.config.pools_path

-- 
To view, visit https://gerrit.wikimedia.org/r/215891
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I87147db34746d9f9b02cc93d97fecaec69aa6912
Gerrit-PatchSet: 1
Gerrit-Project: operations/software/conftool
Gerrit-Branch: master
Gerrit-Owner: Giuseppe Lavagetto <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to