From c750226a3b0516547ac9e8f4a34fab020ce402ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 03:48:01 +0200 Subject: [PATCH] We don't need no namespaces ... but let's it customizable through a var env if needed... --- moulinette/__init__.py | 23 +++++-------- moulinette/actionsmap.py | 72 +++++++++++++++++++--------------------- moulinette/globals.py | 1 + 3 files changed, 43 insertions(+), 53 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 0e9188ec..d9badd4c 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -72,13 +72,12 @@ def init(logging_config=None, **kwargs): # Easy access to interfaces -def api(namespaces, host="localhost", port=80, routes={}): +def api(host="localhost", port=80, routes={}): """Web server (API) interface Run a HTTP server with the moulinette for an API usage. Keyword arguments: - - namespaces -- The list of namespaces to use - host -- Server address to bind to - port -- Server port to bind to - routes -- A dict of additional routes to add in the form of @@ -88,31 +87,27 @@ def api(namespaces, host="localhost", port=80, routes={}): from moulinette.actionsmap import ActionsMap from moulinette.interfaces.api import Interface, ActionsMapParser try: - actionsmap = ActionsMap(ActionsMapParser(), - namespaces=namespaces) + actionsmap = ActionsMap(ActionsMapParser()) interface = Interface(actionsmap=actionsmap, routes=routes) interface.run(host, port) except MoulinetteError as e: import logging - - logging.getLogger(namespaces[0]).error(e.strerror) - return e.errno if hasattr(e, "errno") else 1 + logging.getLogger().error(e.strerror) + return 1 except KeyboardInterrupt: import logging - - logging.getLogger(namespaces[0]).info(m18n.g("operation_interrupted")) + logging.getLogger().info(m18n.g("operation_interrupted")) return 0 -def cli(namespaces, args, top_parser, output_as=None, timeout=None): +def cli(args, top_parser, output_as=None, timeout=None): """Command line interface Execute an action with the moulinette from the CLI and print its result in a readable format. Keyword arguments: - - namespaces -- The list of namespaces to use - args -- A list of argument strings - output_as -- Output result in another format, see moulinette.interfaces.cli.Interface for possible values @@ -122,14 +117,12 @@ def cli(namespaces, args, top_parser, output_as=None, timeout=None): from moulinette.actionsmap import ActionsMap from moulinette.interfaces.cli import Interface, ActionsMapParser try: - actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser), - namespaces=namespaces) + actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser)) interface = Interface(actionsmap=actionsmap) interface.run(args, output_as=output_as, timeout=timeout) except MoulinetteError as e: import logging - - logging.getLogger(namespaces[0]).error(e.strerror) + logging.getLogger().error(e.strerror) return 1 return 0 diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index b36e1ece..221a46fb 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -4,6 +4,7 @@ import os import re import logging import yaml +import glob import cPickle as pickle from time import time from collections import OrderedDict @@ -398,16 +399,14 @@ class ActionsMap(object): Moreover, the action can have specific argument(s). This class allows to manipulate one or several actions maps - associated to a namespace. If no namespace is given, it will load - all available namespaces. + associated to a namespace. Keyword arguments: - top_parser -- A BaseActionsMapParser-derived instance to use for parsing the actions map - - namespaces -- The list of namespaces to use """ - def __init__(self, top_parser, namespaces=[]): + def __init__(self, top_parser): assert isinstance(top_parser, BaseActionsMapParser), "Invalid parser class '%s'" % top_parser.__class__.__name__ @@ -415,12 +414,10 @@ class ActionsMap(object): DATA_DIR = moulinette_env["DATA_DIR"] CACHE_DIR = moulinette_env["CACHE_DIR"] - if len(namespaces) == 0: - namespaces = self.get_namespaces() actionsmaps = OrderedDict() # Iterate over actions map namespaces - for n in namespaces: + for n in self.get_namespaces(): logger.debug("loading actions map namespace '%s'", n) actionsmap_yml = "%s/actionsmap/%s.yml" % (DATA_DIR, n) @@ -441,10 +438,10 @@ class ActionsMap(object): # TODO: Switch to python3 and catch proper exception except (IOError, EOFError): self.from_cache = False - actionsmaps = self.generate_cache(namespaces) + actionsmaps[n] = self.generate_cache(n) else: # cache file doesn't exists self.from_cache = False - actionsmaps = self.generate_cache(namespaces) + actionsmaps[n] = self.generate_cache(n) # Load translations m18n.load_namespace(n) @@ -587,56 +584,55 @@ class ActionsMap(object): moulinette_env = init_moulinette_env() DATA_DIR = moulinette_env["DATA_DIR"] - for f in os.listdir("%s/actionsmap" % DATA_DIR): - if f.endswith(".yml"): - namespaces.append(f[:-4]) + # This var is ['*'] by default but could be set for example to + # ['yunohost', 'yml_*'] + NAMESPACE_PATTERNS = moulinette_env["NAMESPACES"] + + # Look for all files that match the given patterns in the actionsmap dir + for namespace_pattern in NAMESPACE_PATTERNS: + namespaces.extend(glob.glob("%s/actionsmap/%s.yml" % (DATA_DIR, namespace_pattern))) + + # Keep only the filenames with extension + namespaces = [os.path.basename(n)[:-4] for n in namespaces] + return namespaces @classmethod - def generate_cache(klass, namespaces=None): + def generate_cache(klass, namespace): """ Generate cache for the actions map's file(s) Keyword arguments: - - namespaces -- A list of namespaces to generate cache for + - namespace -- The namespace to generate cache for Returns: - A dict of actions map for each namespaces - + The action map for the namespace """ moulinette_env = init_moulinette_env() CACHE_DIR = moulinette_env["CACHE_DIR"] DATA_DIR = moulinette_env["DATA_DIR"] - actionsmaps = {} - if not namespaces: - namespaces = klass.get_namespaces() - # Iterate over actions map namespaces - for n in namespaces: - logger.debug("generating cache for actions map namespace '%s'", n) + logger.debug("generating cache for actions map namespace '%s'", namespace) - # Read actions map from yaml file - am_file = "%s/actionsmap/%s.yml" % (DATA_DIR, n) - with open(am_file, "r") as f: - actionsmaps[n] = ordered_yaml_load(f) + # Read actions map from yaml file + am_file = "%s/actionsmap/%s.yml" % (DATA_DIR, namespace) + with open(am_file, "r") as f: + actionsmap = ordered_yaml_load(f) - # at installation, cachedir might not exists - if os.path.exists("%s/actionsmap/" % CACHE_DIR): - # clean old cached files - for i in os.listdir("%s/actionsmap/" % CACHE_DIR): - if i.endswith(".pkl"): - os.remove("%s/actionsmap/%s" % (CACHE_DIR, i)) + # at installation, cachedir might not exists + for old_cache in glob.glob("%s/actionsmap/%s-*.pkl" % (CACHE_DIR, namespace)): + os.remove(old_cache) - # Cache actions map into pickle file - am_file_stat = os.stat(am_file) + # Cache actions map into pickle file + am_file_stat = os.stat(am_file) - pkl = "%s-%d-%d.pkl" % (n, am_file_stat.st_size, am_file_stat.st_mtime) + pkl = "%s-%d-%d.pkl" % (namespace, am_file_stat.st_size, am_file_stat.st_mtime) - with open_cachefile(pkl, "w", subdir="actionsmap") as f: - pickle.dump(actionsmaps[n], f) + with open_cachefile(pkl, "w", subdir="actionsmap") as f: + pickle.dump(actionsmap, f) - return actionsmaps + return actionsmap # Private methods diff --git a/moulinette/globals.py b/moulinette/globals.py index 39f45d93..025aab52 100644 --- a/moulinette/globals.py +++ b/moulinette/globals.py @@ -11,4 +11,5 @@ def init_moulinette_env(): "MOULINETTE_LOCALES_DIR", "/usr/share/moulinette/locale" ), "CACHE_DIR": environ.get("MOULINETTE_CACHE_DIR", "/var/cache/moulinette"), + "NAMESPACES": environ.get("MOULINETTE_NAMESPACES", "*").split(), # By default we'll load every namespace we find }