fanch 1 год назад
Родитель
Сommit
a10b495076

+ 5 - 5
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, use
+from metadocker.cmdline.commands.main import init, list, config, manage, use, remove
 import sys
 
 
@@ -22,13 +22,13 @@ class Args(argparse.ArgumentParser):
         ret = self.parse_args(args)
         if hasattr(ret, "command"):
             if env is not None:
-                ret.command(ret).start(env)
+                ret = ret.command(self, ret).start(env)
             else:
-                ret.command(ret).start()
+                ret = ret.command(self, ret).start()
 
         else:
             self.print_help()
-            exit(-1)
+            return -1
         return ret
 
     def _add_command(self, classe):
@@ -59,7 +59,7 @@ class ArgsFromFile(Args):
 
 class MainArgs(Args):
     _COMMANDS = [init.InitCommand, list.ListCommand, config.ConfigCommand, manage.ManageCommand,
-                 use.UseCommand]
+                 use.UseCommand, remove.RemoveCommand]
 
     def __init__(self):
         super().__init__()

+ 37 - 3
src/metadocker/cmdline/commands/base.py

@@ -1,3 +1,4 @@
+import argparse
 import sys
 
 from metadocker.common.errors import MetadockerError
@@ -8,10 +9,40 @@ class Argument:
     def __init__(self, *args, **kwargs):
         self.args = args
         self.kwargs = kwargs
+        self.parser = None
 
     def call(self, parser):
+        self.parser = parser
         parser.add_argument(*self.args, **self.kwargs)
 
+class ArgumentSubCommands(Argument):
+    def __init__(self, name, commands, command_name, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.name = name
+        self.commands = commands
+        self.command_name = command_name
+        self.subparsers = None
+
+    def call(self, parser):
+        self.parser = parser
+        self.subparsers =  parser.add_subparsers(prog=self.name, parser_class=argparse.ArgumentParser)
+        for k in self.commands:
+            self._add_command(classe=k)
+
+
+
+    def _add_command(self, classe):
+        kwargs = {}
+        if classe.HELP:
+            kwargs["help"] = classe.HELP
+        if classe.ALIASES:
+            kwargs["aliases"] = classe.ALIASES
+
+        parser = self.subparsers.add_parser(classe.NAME, **kwargs)
+        for k in classe.ARGUMENTS:
+            k.call(parser)
+        parser.set_defaults(**{self.command_name:classe})
+        return parser
 
 class Command:
     NAME = None
@@ -20,8 +51,9 @@ class Command:
     ARGUMENTS = []
     CATCH_EXCEPTION = True
     CONFIG_SET = True
-    def __init__(self, data):
+    def __init__(self, parser, data):
         self._data = data
+        self._parser = parser
         data = data.__dict__
 
         for k, v in data.items():
@@ -44,10 +76,11 @@ class Command:
     def start(self):
         if self.check():
             try:
-                self.run()
+                return self.run()
             except MetadockerError as err:
                 print("Erreur", file=sys.stderr)
                 print(err.get_error(), file=sys.stderr)
+                return -1
 
     def run(self):
         raise NotImplementedError()
@@ -61,10 +94,11 @@ class SpecificCommand(Command):
         if self.check():
             try:
                 env.read()
-                self.run(env)
+                return self.run(env)
             except MetadockerError as err:
                 print("Erreur", file=sys.stderr)
                 print(err.get_error(), file=sys.stderr)
+                return -1
 
 
 

+ 1 - 0
src/metadocker/cmdline/commands/main/config.py

@@ -22,6 +22,7 @@ class ConfigCommand(Command):
     ]
 
     def run(self):
+        settings.load()
         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)):

+ 38 - 3
src/metadocker/cmdline/commands/main/list.py

@@ -1,4 +1,8 @@
+from collections import defaultdict
+
 from metadocker.cmdline.commands.base import Argument, Command
+from metadocker.common.errors import SimpleError
+from metadocker.common.misc import aligned_table
 from metadocker.db.db import settings
 from metadocker.db.manager import DockerManager
 from metadocker.register import load_env
@@ -8,10 +12,41 @@ class ListCommand(Command):
     NAME = "list"
     HELP = "Liste les scripts"
     ARGUMENTS = [
-
+        Argument("--short", "-s", action="store_true", help="Afficher simplement la liste"),
+        Argument("--long", "-l", action="store_true", help="Affichage long"),
+        Argument("--fields", "-f",
+                 help="La liste des champs à afficher séparé par des virgules",
+                 default="name,PROJECT,DOCKER_NAME,PORTS,VOLUMES,running")
     ]
 
     def run(self):
-        print(f"Environnement disponibles:")
         manager : DockerManager = settings.app_docker_cache
-        manager.refresh()
+        manager.refresh()
+
+        def _getattr(docker, k):
+            return getattr(docker.meta, k,
+                            getattr(docker, k, ""))
+
+
+        fields = self.fields.split(",")
+        if self.short:
+            print(f"Dockers disponibles: ", "\n" if manager.dockers else "", "\n  ".join(manager.dockers))
+        elif  self.long:
+            content=[]
+            for name, docker in manager.dockers.items():
+                dock = []
+                for k in fields:
+                    dock.append(f"{k}={_getattr(docker, k)}")
+                content.append("\n".join(dock))
+            if content:
+                print("\n\n".join(content))
+        else:
+            table = [fields]
+            for name in sorted(manager.dockers):
+                line = []
+                dock = manager.dockers[name]
+                for k in fields:
+                    line.append(_getattr(dock, k))
+                table.append(line)
+            print(aligned_table(table))
+

+ 17 - 0
src/metadocker/cmdline/commands/main/remove.py

@@ -0,0 +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 RemoveCommand(Command):
+    NAME = "remove"
+    HELP = "Supprime un scripts"
+    ARGUMENTS = [
+        Argument("--keep-data", "-k", action="store_true", help="Ne supprime pas les fichiers"),
+        Argument("name", help="Nom du docker")
+    ]
+
+    def run(self):
+        manager : DockerManager = settings.app_docker_cache
+        manager.remove(self.name, not self.keep_data)

+ 25 - 3
src/metadocker/cmdline/commands/main/use.py

@@ -1,18 +1,40 @@
 import argparse
 
-from metadocker.cmdline.commands.base import Argument, Command
+from metadocker.cmdline.commands.base import Argument, Command, ArgumentSubCommands
+from metadocker.cmdline.commands import specific
 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")
+        ArgumentSubCommands("parser", commands=[
+            specific.build.BuildCommand,
+            specific.start.StartCommand,
+            specific.stop.StopCommand,
+            specific.run.RunCommand,
+            specific.rm.RmCommand,
+            specific.status.StatusCommand,
+            specific.logs.LogsCommand,
+            specific.systemd.SystemdCommand,
+            specific.shell.ShellCommand
+            ], command_name="sub_command"
+        )
     ]
 
     def run(self):
         docker = settings.app_docker_cache.get(self.name)
-        docker.main(self.metadocker_args)
+        parser = None
+        for arg in self.ARGUMENTS:
+            if isinstance(arg, ArgumentSubCommands) and arg.name == "parser":
+                parser = arg.parser
+                if not getattr(self, "sub_command", None):
+                    arg.parser.print_help()
+                    return -1
+
+        env = docker.get_env_object()
+        return self.sub_command(parser, self._data).start(env)

+ 1 - 1
src/metadocker/cmdline/commands/specific/logs.py

@@ -17,7 +17,7 @@ class LogsCommand(SpecificCommand):
     def run(self, env):
         name = env.docker.name
 
-        for res in driver.ps(name=env.docker.name):
+        for res in driver.get_state(name=env.docker.name):
             driver.logs(env.docker.name,
                         details=self.details,
                         follow=self.follow,

+ 1 - 2
src/metadocker/cmdline/commands/specific/run.py

@@ -17,7 +17,7 @@ class RunCommand(SpecificCommand):
     ]
 
     def _rm_if_needed(self, env):
-        for data in driver.ps(name=env.docker.name):
+        for data in driver.get_state(name=env.docker.name):
             if data.running:
                 raise SimpleError(f"Erreur le conteneur '{env.docker.name}' est déja lancé")
             driver.rm(env.docker.name)
@@ -49,7 +49,6 @@ class RunCommand(SpecificCommand):
         kwargs = {"catch": False}
         if self.interactive:
             kwargs["stdin"]=sys.stdin
-        data = driver.ps(name=env.docker.name)
 
 
         driver.run(*cmdline, **kwargs)

+ 1 - 1
src/metadocker/cmdline/commands/specific/status.py

@@ -12,7 +12,7 @@ class StatusCommand(SpecificCommand):
     ]
     def run(self, env):
         name = env.docker.name
-        data = driver.ps(name=name, classe=dict)
+        data = driver.get_state(name=name, classe=dict)
         if data:
             print(json.dumps(data[0], indent = 2))
             return 0

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

@@ -1,6 +1,7 @@
 import inspect
 import os
 import sys
+from collections import defaultdict
 from pathlib import Path
 
 
@@ -19,4 +20,23 @@ def flatten(input, out=None):
             flatten(x, out)
         else:
             out.append(x)
-    return out
+    return out
+
+def aligned_table(table, sep="\t"):
+    sizes = defaultdict(int)
+    for line in table:
+        for i, val in list(enumerate(line)):
+            val = str(val)
+            line[i] = val
+            sizes[i] = max(sizes[i], len(val))
+
+    for line in table:
+        for i, val in enumerate(line):
+            line[i] = val.ljust(sizes[i], " ")
+    content = "\n".join([
+        sep.join(
+            val.ljust(sizes[i], " ")
+            for i, val in enumerate(line)
+        ) for line in table
+    ])
+    return content

+ 7 - 5
src/metadocker/db/db.py

@@ -4,7 +4,8 @@ import pickle
 from pathlib import Path
 
 from metadocker.common.errors import SimpleError, MultipleError, MetadockerError
-from metadocker.common.var import VarPath, VarList, EmptyObject, Var, VarDict, NotEmpty
+from metadocker.common.var import VarPath, VarList, EmptyObject, Var, VarDict, NotEmpty, VarInt
+
 
 class DockerManagerBase:
     pass
@@ -22,11 +23,12 @@ class _Config:
 
     APP__DIR = "app.dir"
     APP__DOCKER_CACHE = "app.docker.cache"
+    APP__NEXT_PORT = "app.next_port"
 
     _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)
-
+        VarDockerManager(APP__DOCKER_CACHE, default_value={}, hidden=True),
+        VarInt(APP__NEXT_PORT, default_value=8201, required=True)
     ]
 
 
@@ -42,7 +44,6 @@ class _Config:
         try:
             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()
 
@@ -102,7 +103,7 @@ class _Config:
             if var.name in self.data:
                 setattr(obj, var.name, self.data[var.name])
             try:
-                var.run_validation(obj)
+                self.data[var.name] = var.run_validation(obj)
             except MetadockerError as err:
                 try:
                     self.data[var.name] = var.on_error(obj, err)
@@ -115,6 +116,7 @@ class _Config:
 
         if errors and raise_exception:
             raise MultipleError(errors)
+        self.save()
         return errors
 
 

+ 17 - 4
src/metadocker/db/entry.py

@@ -6,6 +6,7 @@ from contextlib import chdir
 from pathlib import Path
 
 from metadocker.db.db import settings
+from metadocker.docker.driver import driver
 from metadocker.register import load_env
 
 import sys
@@ -90,6 +91,7 @@ class DockerEntry:
     def __init__(self, name):
         self.name = name
         self.root_dir = settings.app_dir / self.name
+        self.ports = None
         self._init()
 
     def _init(self):
@@ -106,6 +108,8 @@ class DockerEntry:
     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
@@ -116,6 +120,16 @@ class DockerEntry:
     def refresh(self):
         self._env = self._get_env()
         self._meta = self._env.get_meta()
+        self._meta.name = self.name
+
+
+
+    @property
+    def running(self):
+        if not self._meta:
+            self.refresh()
+        return driver.is_running(self._meta.DOCKER_NAME)
+
 
     @property
     def meta(self):
@@ -149,8 +163,7 @@ class DockerEntry:
     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)
+    def get_env_object(self):
+        if not self._env: self.refresh()
+        return self._env

+ 10 - 3
src/metadocker/docker/driver.py

@@ -56,7 +56,7 @@ class DockerDriver:
 
 
     def is_running(self, name):
-        for data in self.ps(name=name):
+        for data in self.get_state(name=name):
             if data.running:
                 return True
         return False
@@ -76,16 +76,23 @@ class DockerDriver:
         self._docker("start", *args, **kwarsg)
 
 
-    def ps(self, classe=DockerContainer, **filters):
+    def ps(self, classe=DockerContainer, with_name=None, **filters):
         cmdline = ["ps", "-a",  "--format", "json", "--no-trunc"]
         for k, v in filters.items():
             cmdline.extend(("--filter", f"{k}={v}"))
 
         ret = self._docker(*cmdline)
-        content = [classe(json.loads(x)) for x in ret.stdout.split(b"\n") if x]
+        content = [json.loads(x)
+                   for x in ret.stdout.split(b"\n") if x]
+        content = [classe(x) if classe else x
+                   for x in content
+                   if not with_name or x["Names"] == with_name]
         return content
 
 
+    def get_state(self, name, **kwargs):
+        return  self.ps(with_name=name,**kwargs)
+
     def stop(self, name, time=None, signal=None, **kwargs):
         cmdlint = ["stop", name]
         if time is not None:

+ 6 - 4
src/metadocker/env/base.py

@@ -56,14 +56,16 @@ class Env:
                help="Nom du conteneur docker. Par défaut {env.PROJECT.lower()}", ask=True),
 
 
-        VarDict("ENV", {}, required=True, help="La liste des variable d'environnemnt à utiliser"),
-        VarDict("PORTS", {8101: 8000}, required=True,
+        VarDict("ENV", {}, is_meta=True, required=True, help="La liste des variable d'environnemnt à utiliser"),
+        VarDict("PORTS", {8101: 8000}, required=True,  is_meta=True,
                 help="Le mapping des ports. En clé les ports de l'hote et en valeur les port du container"),
         VarDict("VOLUMES", {"./data": "/data"}, required=True,
                 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, validate=NotEmpty, help="Le dossier ou est lancé le projet", ask=True),
+        VarDict("COPY", {}, required=True,  is_meta=True,
+                help="Dictionaire des fichier a copier: cle: fs hote valeur fs invite"),
+        VarPath("APP_DIR", "/data/data", required=True,  is_meta=True,
+                validate=NotEmpty, help="Le dossier ou est lancé le projet", ask=True),
 
     ]
     _name_ = None

+ 3 - 0
src/metadocker/scripts/metadocker

@@ -1,7 +1,10 @@
 #!/bin/env python3
 from metadocker.cmdline.base import MainArgs
+from metadocker.db.db import settings
+
 
 def main():
+    settings.load()
     MainArgs.main(None)
 
 main()

+ 1 - 1
src/metadocker/scripts/metarun

@@ -7,6 +7,6 @@ from metadocker.db.db import settings
 
 def main():
     settings.load()
-    MainArgs.main(args=["use"] + sys.argv[1:])
+    exit(MainArgs.main(args=["use"] + sys.argv[1:]))
 
 main()