فهرست منبع

- création de la commande

fanch 1 سال پیش
والد
کامیت
970c7a09fb

+ 3 - 2
setup.py

@@ -26,7 +26,7 @@ def get_files(path):
 
 setup(
     name="metadocker",
-    version="0.1.2",
+    version="0.2.0",
     description="Un outil pour gérer des conteneurs",
     author="François GAUTRAIS",
     author_email="francois@gautrais.eu",
@@ -43,7 +43,8 @@ setup(
         "pylint": ["pylint"],
     },
     scripts=[
-      "src/metadocker/scripts/metadocker"
+      "src/metadocker/scripts/metadocker",
+      "src/metadocker/scripts/metarun"
     ],
 
     entry_points={

+ 1 - 1
src/metadocker.egg-info/PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: metadocker
-Version: 0.1.2
+Version: 0.2.0
 Summary: Un outil pour gérer des conteneurs
 Author: François GAUTRAIS
 Author-email: francois@gautrais.eu

+ 18 - 3
src/metadocker.egg-info/SOURCES.txt

@@ -29,14 +29,20 @@ src/metadocker/cmdline/commands/__pycache__/__init__.cpython-38.pyc
 src/metadocker/cmdline/commands/__pycache__/base.cpython-311.pyc
 src/metadocker/cmdline/commands/__pycache__/base.cpython-38.pyc
 src/metadocker/cmdline/commands/main/__init__.py
+src/metadocker/cmdline/commands/main/config.py
 src/metadocker/cmdline/commands/main/init.py
 src/metadocker/cmdline/commands/main/list.py
+src/metadocker/cmdline/commands/main/manage.py
+src/metadocker/cmdline/commands/main/use.py
 src/metadocker/cmdline/commands/main/__pycache__/__init__.cpython-311.pyc
 src/metadocker/cmdline/commands/main/__pycache__/__init__.cpython-38.pyc
+src/metadocker/cmdline/commands/main/__pycache__/config.cpython-311.pyc
 src/metadocker/cmdline/commands/main/__pycache__/init.cpython-311.pyc
 src/metadocker/cmdline/commands/main/__pycache__/init.cpython-38.pyc
 src/metadocker/cmdline/commands/main/__pycache__/list.cpython-311.pyc
 src/metadocker/cmdline/commands/main/__pycache__/list.cpython-38.pyc
+src/metadocker/cmdline/commands/main/__pycache__/manage.cpython-311.pyc
+src/metadocker/cmdline/commands/main/__pycache__/use.cpython-311.pyc
 src/metadocker/cmdline/commands/specific/__init__.py
 src/metadocker/cmdline/commands/specific/build.py
 src/metadocker/cmdline/commands/specific/logs.py
@@ -69,12 +75,22 @@ src/metadocker/cmdline/commands/specific/__pycache__/systemd.cpython-38.pyc
 src/metadocker/common/__init__.py
 src/metadocker/common/errors.py
 src/metadocker/common/misc.py
+src/metadocker/common/var.py
 src/metadocker/common/__pycache__/__init__.cpython-311.pyc
 src/metadocker/common/__pycache__/__init__.cpython-38.pyc
 src/metadocker/common/__pycache__/errors.cpython-311.pyc
 src/metadocker/common/__pycache__/errors.cpython-38.pyc
 src/metadocker/common/__pycache__/misc.cpython-311.pyc
 src/metadocker/common/__pycache__/misc.cpython-38.pyc
+src/metadocker/common/__pycache__/var.cpython-311.pyc
+src/metadocker/db/__init__.py
+src/metadocker/db/db.py
+src/metadocker/db/entry.py
+src/metadocker/db/manager.py
+src/metadocker/db/__pycache__/__init__.cpython-311.pyc
+src/metadocker/db/__pycache__/db.cpython-311.pyc
+src/metadocker/db/__pycache__/entry.cpython-311.pyc
+src/metadocker/db/__pycache__/manager.cpython-311.pyc
 src/metadocker/docker/__init__.py
 src/metadocker/docker/builder.py
 src/metadocker/docker/driver.py
@@ -87,13 +103,12 @@ src/metadocker/docker/__pycache__/driver.cpython-38.pyc
 src/metadocker/env/__init__.py
 src/metadocker/env/base.py
 src/metadocker/env/pythonserver.py
-src/metadocker/env/var.py
 src/metadocker/env/__pycache__/__init__.cpython-311.pyc
 src/metadocker/env/__pycache__/__init__.cpython-38.pyc
 src/metadocker/env/__pycache__/base.cpython-311.pyc
 src/metadocker/env/__pycache__/base.cpython-38.pyc
 src/metadocker/env/__pycache__/pythonserver.cpython-311.pyc
 src/metadocker/env/__pycache__/pythonserver.cpython-38.pyc
-src/metadocker/env/__pycache__/var.cpython-311.pyc
 src/metadocker/scripts/__init__.py
-src/metadocker/scripts/metadocker
+src/metadocker/scripts/metadocker
+src/metadocker/scripts/metarun

+ 2 - 0
src/metadocker/__main__.py

@@ -1,7 +1,9 @@
 from metadocker.cmdline.base import MainArgs
+from metadocker.db.db import settings
 from metadocker.env.base import Env
 
 
+settings.load()
 def main():
     MainArgs.main()
 

+ 3 - 2
src/metadocker/cmdline/base.py

@@ -1,7 +1,7 @@
 import argparse
 from metadocker.cmdline.commands.specific import start, stop, build, run, \
     rm, status, logs, systemd, shell
-from metadocker.cmdline.commands.main import init, list, config, manage
+from metadocker.cmdline.commands.main import init, list, config, manage, use
 import sys
 
 
@@ -58,7 +58,8 @@ class ArgsFromFile(Args):
         super().__init__()
 
 class MainArgs(Args):
-    _COMMANDS = [init.InitCommand, list.ListCommand, config.ConfigCommand, manage.ManageCommand]
+    _COMMANDS = [init.InitCommand, list.ListCommand, config.ConfigCommand, manage.ManageCommand,
+                 use.UseCommand]
 
     def __init__(self):
         super().__init__()

+ 28 - 9
src/metadocker/cmdline/commands/base.py

@@ -1,6 +1,7 @@
 import sys
 
 from metadocker.common.errors import MetadockerError
+from metadocker.db.db import settings
 
 
 class Argument:
@@ -11,12 +12,14 @@ class Argument:
     def call(self, parser):
         parser.add_argument(*self.args, **self.kwargs)
 
+
 class Command:
     NAME = None
     HELP = None
     ALIASES = []
     ARGUMENTS = []
     CATCH_EXCEPTION = True
+    CONFIG_SET = True
     def __init__(self, data):
         self._data = data
         data = data.__dict__
@@ -24,12 +27,27 @@ class Command:
         for k, v in data.items():
             setattr(self, k, v)
 
-    def start(self):
+    def check(self, raise_exception=False):
         try:
-            self.run()
+            if self.CONFIG_SET:
+                settings.validate()
+
         except MetadockerError as err:
-            print("Erreur", file=sys.stderr)
+            print("Erreur de configuration interne à métadocker", file=sys.stderr)
             print(err.get_error(), file=sys.stderr)
+            if raise_exception:
+                raise err
+            return False
+        return True
+
+
+    def start(self):
+        if self.check():
+            try:
+                self.run()
+            except MetadockerError as err:
+                print("Erreur", file=sys.stderr)
+                print(err.get_error(), file=sys.stderr)
 
     def run(self):
         raise NotImplementedError()
@@ -40,12 +58,13 @@ class SpecificCommand(Command):
 
     def start(self, env):
         env.debug = self.debug
-        try:
-            env.read()
-            self.run(env)
-        except MetadockerError as err:
-            print("Erreur", file=sys.stderr)
-            print(err.get_error(), file=sys.stderr)
+        if self.check():
+            try:
+                env.read()
+                self.run(env)
+            except MetadockerError as err:
+                print("Erreur", file=sys.stderr)
+                print(err.get_error(), file=sys.stderr)
 
 
 

+ 29 - 4
src/metadocker/cmdline/commands/main/config.py

@@ -2,6 +2,7 @@ import sys
 from pathlib import Path
 
 from metadocker.cmdline.commands.base import Argument, Command
+from metadocker.common.errors import MetadockerError
 from metadocker.db.db import settings
 from metadocker.register import load_env
 
@@ -11,23 +12,43 @@ class ConfigCommand(Command):
     HELP = "Configure l'application\nEx metadocker config -l # liste les config\n" \
            "metadocker config my.key # renvoie la valeur de my.key\n" \
            "metadocker config \"my.key=my value\" # affctre la valeur \"my value\" à my.key"
+    CONFIG_SET = False
 
     ARGUMENTS = [
         Argument("--list", "-l", action="store_true", help="Liste les configurations"),
         Argument("--short", "-s", action="store_true", help="N'affiche pas les clés"),
+        Argument("--validate", "-v", action="store_true", help="Vérifie la configuration"),
         Argument("args",  nargs="*", help="Les ")
     ]
 
     def run(self):
-        if self.list or not self.args:
-            for k, v in settings.items():
-                print(f"{k}={v}")
+        vars = {x.name: x for x in settings._vars_}
+        if  not self.args and not self.validate:
+            for k in sorted(set(settings)|set(vars)):
+                if k in vars:
+                    var = vars[k]
+                    if var.help:
+                        help = var.help.replace('\n','\n# ')
+                        print(f"# {k}: {help}")
+                    if k in settings:
+                        print(f"{k}={settings[k]}")
+                    else:
+                        print(f"{k} : Non défini")
+                else:
+                    print(f"{k}={k}")
             exit(0)
 
         for arg in self.args:
             if "=" in arg:
                 key, _, value = arg.partition("=")
-                settings[key] = value
+                if key in vars:
+                    try:
+                        settings[key] = vars[key].to_python(value)
+                    except MetadockerError as err:
+                        print(f"Erreur de saisie")
+                        print(err.get_error())
+                else:
+                    settings[key] = value
             else:
                 if arg in settings:
                     if self.short:
@@ -36,3 +57,7 @@ class ConfigCommand(Command):
                         print(f"{arg}={settings[arg]}")
                 else:
                     print(f"Clé {arg} introuvable", file=sys.stderr)
+
+        if self.validate:
+            if self.check(raise_exception=True):
+                print(f"La configuration est valide")

+ 12 - 14
src/metadocker/cmdline/commands/main/init.py

@@ -2,29 +2,27 @@ import sys
 from pathlib import Path
 
 from metadocker.cmdline.commands.base import Argument, Command
-from metadocker.register import load_env
-
+from metadocker.db.db import settings
 
 class InitCommand(Command):
     NAME = "init"
     HELP = "Initialise un script"
     ARGUMENTS = [
         Argument("env", help="Le type d'environnement à initialiser"),
-        Argument("--output", "-o", action="store_true", help="Le fichier destination", default="./docker")
+        Argument("name", help="Le nom du metadocekr"),
+        Argument("--values", "-v", nargs="*", help="Les valeurs à remplir de la forme: KEY= KEY2=ARG")
     ]
 
     def run(self):
-        env_name = self.env.lower()
-        envs = load_env()
-        if env_name not in envs:
-            print(f"Impossible de trouver l'environnement '{self.env}'", file=sys.stderr)
-            exit(-1)
 
-        output = Path(self.output)
-        output.parent.mkdir(exist_ok=True, parents=True)
-        env = envs[env_name]()
+        values = {"PROJECT" : self.name}
+        for value in (self.values or []):
+            if "=" in value:
+                key, _, value = value.partition("=")
+            else:
+                key=value
+                value=""
+            values[key] = value
 
-        content = env.create_empty_config()
-        output.write_text(content)
-        output.chmod(0o750)
 
+        settings.app_docker_cache.create(self.env.lower(), self.name, values)

+ 6 - 6
src/metadocker/cmdline/commands/main/list.py

@@ -1,17 +1,17 @@
 from metadocker.cmdline.commands.base import Argument, Command
+from metadocker.db.db import settings
+from metadocker.db.manager import DockerManager
 from metadocker.register import load_env
 
 
 class ListCommand(Command):
-    NAME = "liste"
+    NAME = "list"
     HELP = "Liste les scripts"
     ARGUMENTS = [
+
     ]
 
     def run(self):
         print(f"Environnement disponibles:")
-        for v in load_env().values():
-            print(v._name_)
-
-
-
+        manager : DockerManager = settings.app_docker_cache
+        manager.refresh()

+ 18 - 0
src/metadocker/cmdline/commands/main/use.py

@@ -0,0 +1,18 @@
+import argparse
+
+from metadocker.cmdline.commands.base import Argument, Command
+from metadocker.db.db import settings
+from metadocker.db.manager import DockerManager
+
+
+class UseCommand(Command):
+    NAME = "use"
+    HELP = "Utilise le docker"
+    ARGUMENTS = [
+        Argument("name", help="Le nom du docker"),
+        Argument("metadocker_args", nargs=argparse.REMAINDER, help="Les argument à passer")
+    ]
+
+    def run(self):
+        docker = settings.app_docker_cache.get(self.name)
+        docker.main(self.metadocker_args)

+ 10 - 0
src/metadocker/common/errors.py

@@ -15,3 +15,13 @@ class SimpleError(MetadockerError):
 
     def get_error(self):
         return repr(self._err)
+
+
+class MultipleError(MetadockerError):
+
+    def __init__(self, errors):
+        super().__init__(errors)
+        self._errors = errors
+
+    def get_error(self):
+        return "\n".join([x.get_error() for x in self._errors])

+ 15 - 1
src/metadocker/common/misc.py

@@ -5,4 +5,18 @@ from pathlib import Path
 
 
 def get_root_dir():
-    return Path(sys.argv[0]).parent.resolve()
+    return Path(sys.argv[0]).parent.resolve()
+
+
+def flatten(input, out=None):
+    out = out if out is not None else []
+    if not isinstance(input, (list, tuple, set)):
+        input = [input]
+    if isinstance(out, (tuple, set)):
+        out = list(out)
+    for x in input:
+        if isinstance(x, (list, tuple, set)):
+            flatten(x, out)
+        else:
+            out.append(x)
+    return out

+ 75 - 51
src/metadocker/env/var.py → src/metadocker/common/var.py

@@ -1,6 +1,9 @@
+import json
+import pickle
 from pathlib import Path
 
 from metadocker.common.errors import MetadockerError
+from metadocker.common.misc import flatten
 
 NoneType=None.__class__
 
@@ -13,12 +16,10 @@ def NotEmpty(x):
         raise Var.ValidationException(None, f"doit être non vide")
     return x
 
-def get_types(x):
-    if x is None:
-        return tuple()
-    if not isinstance(x, (list, tuple, set)):
-        return (x,)
-    return x
+def get_types(types):
+    if not isinstance(types, (tuple, list, set)):
+        types = [types]
+    return tuple([x.__class__ if not isinstance(x, type) else x for x in types])
 
 class EmptyObject:
     def __init__(self, **kwargs):
@@ -26,7 +27,9 @@ class EmptyObject:
             setattr(self, k, v)
 
 class Var:
-
+    _expected_types_ = []
+    def _validate(self, value):
+        return value
     class VarException(MetadockerError):
         def get_error(self):
             raise NotImplementedError()
@@ -39,7 +42,7 @@ class Var:
         def get_error(self):
             return f"La variable {self.name} est requise"
 
-    class ByPassException(Exception):
+    class ByPassException(MetadockerError):
         pass
 
     class ValidationException(VarException):
@@ -66,22 +69,30 @@ class Var:
             return f"La variable {self.name} doit être de type {'('+', '.join(self.types)+')'}. Type trouvé {self.found.__class__.__name__}"
 
     def __init__(self, name, default_value=EmptyValue, required=False, validate=None,
-                 expected_types=None, help=None, is_meta=False, null=False, ask=False):
+                 expected_types=None, help=None, is_meta=False, null=False, ask=False, **kwargs):
         self.name = name
         self.default_value = default_value
         self.required = required
-        self._validate = validate if validate else lambda x: x
-        self.expected_types = expected_types
+        if validate:
+            self._validate = validate
+        self.expected_types = get_types(flatten(self._expected_types_ or [],  expected_types or []))
         self.help = help
-        self.null = null
+        self.null = null or self.default_value is None
         self.is_meta = is_meta
         self.ask = ask
+        self.kwargs = kwargs
+
+
+    def on_error(self, obj, err):
+        raise err
 
     @property
     def has_default(self):
         return self.default_value is not EmptyValue
 
-    def validate(self, obj):
+    def run_validation(self, obj, is_parent = True):
+        if not is_parent:
+            obj = EmptyObject(**{self.name : obj})
         if not hasattr(obj, self.name):
             if self.default_value is EmptyValue:
                 if self.required:
@@ -105,12 +116,29 @@ class Var:
     def __repr__(self):
         return f"<{self.__class__.__name__} {self.name}>"
 
-    def _parse(self, type, value):
+
+    def run_to_python(self, value):
         if self.null and value == "None":
             return None
-        return type(value)
+        return self.to_python(value)
+
+    def to_python(self, value):
+        if not self.expected_types:
+            return value
+        for t in self.expected_types:
+            try:
+                v = t(value)
+                return self.run_validation(v, is_parent=False)
+            except Exception as err:
+                continue
+
+        if self.null and not value:
+            return self.default_value
+        raise self.ValidationException(self.name, f"Impossible de parser '{value}'")
 
-    def ask_value(self):
+    def ask_value(self, **kwargs):
+        has_value = "value" in kwargs
+        value = kwargs.get("value")
         while True:
             print(self.help)
             prompt = [self.name]
@@ -119,24 +147,22 @@ class Var:
             if has_default:
                 prompt.append(f" [{self.default_value}]")
 
-            value = input("".join(prompt)+": ")
+            if not has_value:
+                value = input("".join(prompt)+": ")
+            else:
+                print("".join(prompt) + ":", value)
+                has_value = False
+
 
             if not has_default and not value:
                 print(f"Merci de donner une réponse")
                 continue
 
-            try:
-                parsed_value = None
-                for x in self.expected_types or []:
-                    try:
-                        parsed_value = self._parse(x, value)
-                        return self.validate(EmptyObject(**{self.name: parsed_value}))
-                    except: pass
-                else:
-                    if self.null and not value:
-                        return self.default_value
-                    raise Var.RequiredException(self.name)
+            if value=="":
+                return self.default_value
 
+            try:
+                return self.run_to_python(value)
             except MetadockerError as err:
                 if isinstance(err, self.ValidationException):
                     err.name = self.name
@@ -144,35 +170,33 @@ class Var:
 
 
 class VarStr(Var):
-    def __init__(self, name, default_value=EmptyValue, required=False, validate=NotEmpty, help=None,
-                 is_meta=False, expected_types=None, null=False, ask=False):
-        super().__init__(name, default_value, required, validate, help=help,
-                         expected_types=(str, *get_types(expected_types)), is_meta=is_meta,
-                         null=null or default_value is None, ask=ask)
+    _expected_types_ = (str, )
 
 class VarPath(Var):
-    def __init__(self, name, default_value=EmptyValue, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False, ask=False):
-        super().__init__(name, default_value, required, help=help, is_meta=is_meta,
-                         validate=validate or (lambda x: Path(x)),
-                         expected_types=(str, Path, *get_types(expected_types)), null=null, ask=ask)
-
+    _expected_types_ = (Path, str,)
 
 class VarInt(Var):
-    def __init__(self, name, default_value=EmptyValue, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False, ask=False):
-        super().__init__(name, default_value, required, validate, help=help, is_meta=is_meta,
-                         expected_types=(int, *get_types(expected_types)), null=null, ask=ask)
+    _expected_types_ = (int,)
 
 class VarNumber(Var):
-    def __init__(self, name, default_value=EmptyValue, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False, ask=False):
-        super().__init__(name, default_value, required, validate, help=help, is_meta=is_meta,
-                         expected_types=(float, int, *get_types(expected_types)), null=null, ask=ask)
+    _expected_types_ = (float, int,)
 
 class VarDict(Var):
-    def __init__(self, name, default_value=EmptyValue, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False, ask=False):
-        super().__init__(name, default_value, required, validate, help=help, is_meta=is_meta,
-                         expected_types=(dict, *get_types(expected_types)), null=null, ask=ask)
+    _expected_types_ = (dict,)
+    def  to_python(self, value):
+        if isinstance(value, (bytes, str)):
+            try:
+                return self.run_validation(json.loads(value))
+            except:
+                pass
+            if isinstance(value, bytes):
+                try:
+                    return self.run_validation(pickle.loads(value))
+                except:
+                    pass
+        return super().to_python(value)
+
+class VarList(Var):
+    _expected_types_ = (list,)
+
 

+ 71 - 12
src/metadocker/db/db.py

@@ -3,28 +3,52 @@ import os
 import pickle
 from pathlib import Path
 
-from metadocker.common.errors import SimpleError
+from metadocker.common.errors import SimpleError, MultipleError, MetadockerError
+from metadocker.common.var import VarPath, VarList, EmptyObject, Var, VarDict, NotEmpty
+
+class DockerManagerBase:
+    pass
+class VarDockerManager(Var):
+    _expected_types_ = (DockerManagerBase,)
+
+    def on_error(self, obj, err):
+        from metadocker.db.manager import DockerManager
+        return DockerManager()
 
 
 class _Config:
     _engine = pickle
     _INSTANCE = None
-    DEFAULT_DATA = {
-        "app.docker.max_depth" : 3,
-        "app.docker.cache" : [],
-    }
+
+    APP__DIR = "app.dir"
+    APP__DOCKER_CACHE = "app.docker.cache"
+
+    _vars_ = [
+        VarPath(APP__DIR, required=True, validate=NotEmpty, help="Le dossier qui contient les données (configuration de metadocker)"),
+        VarDockerManager(APP__DOCKER_CACHE, default_value={}, hidden=True)
+
+    ]
+
+
     def __init__(self):
         if _Config._INSTANCE is not None:
             raise ValueError(f"Impossible de lancer pluseir base de données")
-
+        self._var_dict_ = {x.name: x for x in self._vars_}
         self.filename = self._get_path() / f"metadocker.{self._engine.__name__}"
-        self.data = dict(self.DEFAULT_DATA)
+        self.data = None
+
+    def load(self):
+        if self.data is not None: return
         try:
-            self.data.update(self._engine.loads(self.filename.read_bytes()))
+            self.data = self._engine.loads(self.filename.read_bytes())
         except Exception as err:
+            raise err
             print(f"Impossible de trouver la configuration dans {self.filename}. Création")
             self.save()
 
+        return self
+
+
     def _get_path(self):
         return Path(os.environ.get("METADOCKER_CONF_DIR",
                                    Path(os.environ["HOME"]) / ".config" / "metadocker" )).resolve()
@@ -37,6 +61,9 @@ class _Config:
     def __getitem__(self, item):
         return self.data[item]
     def __setitem__(self, item, value):
+        if item in self._var_dict_:
+            var = self._var_dict_[item]
+            value = var.to_python(value)
         self.data[item] = value
         self.save()
 
@@ -52,11 +79,43 @@ class _Config:
     def get(self, k, *args):
         return self.data.get(k, *args)
 
+    def __iter__(self):
+        return iter(self.data)
+
+    def _prop(self, name, example=None):
+        if name not in self.data:
+            value = [f"Option {name} non définie."]
+            if example: value.append(f"metadocker config \#a{name}={example}\"")
+            raise SimpleError("\n".join(value))
+        return self.data[name]
+
+    @property
+    def app_dir(self) -> Path: return self._prop(self.APP__DIR, "/dossier/de/done")
     @property
-    def app_dir(self):
-        if "app.dir" not in self.data:
-            raise SimpleError(f"Option base_dir non définie. Merci d'utilser\n"
-                              f"metadocker config \#app.dirr=/dossier/de/donne\"")
+    def app_docker_cache(self) -> DockerManagerBase: return self._prop(self.APP__DOCKER_CACHE)
+
+
+    def validate(self, raise_exception=True):
+        errors = []
+        for var in self._vars_:
+            obj = EmptyObject()
+            if var.name in self.data:
+                setattr(obj, var.name, self.data[var.name])
+            try:
+                var.run_validation(obj)
+            except MetadockerError as err:
+                try:
+                    self.data[var.name] = var.on_error(obj, err)
+                    self.save()
+                except MetadockerError as err:
+                    errors.append(err)
+
+                if var.kwargs.get("on_error"):
+                    var.kwargs["on_error"]()
+
+        if errors and raise_exception:
+            raise MultipleError(errors)
+        return errors
 
 
 settings = _Config()

+ 156 - 0
src/metadocker/db/entry.py

@@ -0,0 +1,156 @@
+import shutil
+import sys
+from importlib import _bootstrap_external
+from contextlib import chdir
+
+from pathlib import Path
+
+from metadocker.db.db import settings
+from metadocker.register import load_env
+
+import sys
+import importlib.util
+
+# def spec_from_file_location(name, location, *, loader=None,
+#                             submodule_search_locations=_POPULATE):
+#     """Return a module spec based on a file location.
+#
+#     To indicate that the module is a package, set
+#     submodule_search_locations to a list of directory paths.  An
+#     empty list is sufficient, though its not otherwise useful to the
+#     import system.
+#
+#     The loader must take a spec as its only __init__() arg.
+#
+#     """
+#      location = str(Path(location).resolve())
+#
+#
+#     _bootstrap
+#
+#     spec = _bootstrap.ModuleSpec(name, loader, origin=location)
+#     spec._set_fileattr = True
+#
+#     # Pick a loader if one wasn't provided.
+#     if loader is None:
+#         for loader_class, suffixes in _get_supported_file_loaders():
+#             if location.endswith(tuple(suffixes)):
+#                 loader = loader_class(name, location)
+#                 spec.loader = loader
+#                 break
+#         else:
+#             return None
+#
+#     # Set submodule_search_paths appropriately.
+#     if submodule_search_locations is _POPULATE:
+#         # Check the loader.
+#         if hasattr(loader, 'is_package'):
+#             try:
+#                 is_package = loader.is_package(name)
+#             except ImportError:
+#                 pass
+#             else:
+#                 if is_package:
+#                     spec.submodule_search_locations = []
+#     else:
+#         spec.submodule_search_locations = submodule_search_locations
+#     if spec.submodule_search_locations == []:
+#         if location:
+#             dirname = _path_split(location)[0]
+#             spec.submodule_search_locations.append(dirname)
+#
+#     return spec
+
+class LoaderContext:
+
+    def __init__(self, suffix):
+        self.suffix = suffix
+        self.old_fct = None
+        self.env = None
+
+        from metadocker.env.base import Env
+        self._Env = Env
+
+    def __enter__(self):
+        loader = [x for x, y in _bootstrap_external._get_supported_file_loaders() if ".py" in y][0]
+        self.old_fct = _bootstrap_external._get_supported_file_loaders
+        _bootstrap_external._get_supported_file_loaders = lambda : [(loader, [self.suffix])]
+        self._Env._suspend_run_ = True
+        self._Env._current_env = None
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._Env._suspend_run_ = False
+        _bootstrap_external._get_supported_file_loaders=self.old_fct
+        self.env = self._Env._current_env
+
+class DockerEntry:
+    _get_state_bypass_ = ["_env", "_meta", "_env_holder"]
+    _config_file_ = "docker.cfg"
+    def __init__(self, name):
+        self.name = name
+        self.root_dir = settings.app_dir / self.name
+        self._init()
+
+    def _init(self):
+        self._env  = False
+
+    def _get_env(self):
+        with LoaderContext(Path(self._config_file_).suffix) as loader:
+            spec = importlib.util.spec_from_file_location(f"docker_{self.name}", self.root_dir / self._config_file_)
+            module = importlib.util.module_from_spec(spec)
+            spec.loader.exec_module(module)
+
+        return loader.env
+
+    def __getstate__(self):
+        return {k: v for k,v in self.__dict__.items() if k not in self._get_state_bypass_}
+
+    def __setstate__(self, d):
+        self.__dict__.update(d)
+        self._env = None
+        self._meta = None
+        self._env_holder = None
+
+
+    def refresh(self):
+        self._env = self._get_env()
+        self._meta = self._env.get_meta()
+
+    @property
+    def meta(self):
+        if not self._meta:
+            self.refresh()
+        return self._meta
+
+    @property
+    def env(self):
+        if not self._meta:
+            self.refresh()
+            self._env_holder = self._env.get_env()
+        return self._env_holder
+
+    def check(self):
+        if not self._env: self.refresh()
+        return self._env.get_env()
+
+    def create(self, env_name, values):
+
+        envs = load_env()
+        if env_name not in envs:
+            print(f"Impossible de trouver l'environnement '{env_name}'", file=sys.stderr)
+            exit(-1)
+        env = envs[env_name]()
+        content = env.create_empty_config(values)
+        output = settings.app_dir / self.name / self._config_file_
+        output.parent.mkdir(exist_ok=True, parents=True)
+        output.write_text(content)
+        output.chmod(0o750)
+    def remove(self):
+        shutil.rmtree(self.root_dir)
+
+    def main(self, args):
+        if not self._env: self.refresh()
+
+        with chdir(self.root_dir):
+            self._env.main(args)

+ 46 - 0
src/metadocker/db/manager.py

@@ -0,0 +1,46 @@
+import shutil
+
+from metadocker.common.errors import SimpleError
+from metadocker.db.db import DockerManagerBase, settings
+from metadocker.db.entry import DockerEntry
+
+
+
+class DockerManager(DockerManagerBase):
+
+    def __init__(self):
+        self.dockers : dict[str,DockerEntry] = {}
+
+
+    def save(self):
+        settings.save()
+
+    def refresh(self):
+        for v in self.dockers.values():
+            v.refresh()
+
+    def remove(self, name, remove_files=True):
+        docker = self.get(name)
+        if remove_files:
+            docker.remove()
+        self.dockers.pop(name)
+        self.save()
+
+
+    def get(self, name):
+        if name not in self.dockers:
+            raise SimpleError(f"Impossible de trouver le conteneur {name} dans metadocker")
+        return self.dockers[name]
+
+
+    def create(self, env_name, name, values):
+        if name in self.dockers:
+            raise SimpleError(f"Le docker {name} existe déja. Abandon")
+        docker = DockerEntry(name)
+        docker.create(env_name, values)
+        self.dockers[name] = docker
+        self.save()
+
+
+
+

+ 16 - 8
src/metadocker/env/base.py

@@ -6,10 +6,11 @@ from pathlib import Path
 from metadocker.cmdline.base import ArgsFromFile
 from metadocker.common.errors import MetadockerError
 from metadocker.common.misc import get_root_dir
+from metadocker.db.db import settings
 from metadocker.docker import DockerBuilder
-from metadocker.env.var import VarDict, VarStr, VarPath, NotEmpty, Var, EmptyValue
-
+from metadocker.common.var import VarDict, VarStr, VarPath, NotEmpty, Var, EmptyValue
 
+settings.load()
 class EnvHolder:
 
     def __init__(self):
@@ -32,6 +33,7 @@ class EnvHolder:
 
 class Env:
     _current_env = None
+    _suspend_run_ = False
 
     PROJECT : str
     PROJECT_VERSION : str
@@ -61,7 +63,7 @@ class Env:
                 help="Le mapping des ports. En clé les ports de l'hote et en valeur les port du container",
                 is_meta=True, null=True),
         VarDict("COPY", {}, required=True, help="Dictionaire des fichier a copier: cle: fs hote valeur fs invite"),
-        VarPath("APP_DIR", "/data/data", required=True, help="Le dossier ou est lancé le projet", ask=True),
+        VarPath("APP_DIR", "/data/data", required=True, validate=NotEmpty, help="Le dossier ou est lancé le projet", ask=True),
 
     ]
     _name_ = None
@@ -107,7 +109,7 @@ class Env:
         for var in self._vars_:
             if filter and not filter(var): continue
             try:
-                value = var.validate(self)
+                value = var.run_validation(self)
             except Var.ByPassException:
                 value = None
             except Var.VarException as err:
@@ -146,7 +148,7 @@ class Env:
         self.generate_docker_file()
         output.write_text(self.docker.get_file_content())
 
-    def create_empty_config(self, interractive=True):
+    def create_empty_config(self, values=None, interractive=True):
         def _value(v, field):
             if v is EmptyValue:
                 return f"# type in ({', '.join([x.__name__ for x in field.expected_types] or ['*'])})"
@@ -163,8 +165,9 @@ class Env:
         def _help(v):
             return v.replace("\n", "\n#")
 
+        values = values or {}
         if interractive:
-            self.ask_for_data()
+            self.ask_for_data(values)
 
         data = [
                 "#!/bin/env python3",
@@ -187,12 +190,17 @@ class Env:
         return "\n".join(data)
 
     def main(self, args=None):
+        if Env._suspend_run_: return
         self.init()
         ArgsFromFile.main(self, args)
 
 
-    def ask_for_data(self):
+    def ask_for_data(self, values = None):
+        values = values or {}
         for var in self._vars_:
             if var.ask:
-                value = var.ask_value()
+                kwargs = {}
+                if var.name in values:
+                    kwargs["value"] = values[var.name]
+                value = var.ask_value(**kwargs)
                 setattr(self, var.name, value)

+ 1 - 1
src/metadocker/env/pythonserver.py

@@ -5,7 +5,7 @@ from pathlib import Path
 
 from metadocker.docker import DockerBuilder
 from metadocker.env.base import Env
-from metadocker.env.var import VarStr, VarInt, VarPath, VarDict
+from metadocker.common.var import VarStr, VarInt, VarPath, VarDict
 from metadocker.register import register_env
 
 

+ 12 - 0
src/metadocker/scripts/metarun

@@ -0,0 +1,12 @@
+#!/bin/env python3
+import sys
+
+from metadocker.cmdline.base import MainArgs
+from metadocker.db.db import settings
+
+
+def main():
+    settings.load()
+    MainArgs.main(args=["use"] + sys.argv[1:])
+
+main()