Jelajahi Sumber

- unify api

fanch 1 tahun lalu
induk
melakukan
3c43295c74

+ 1 - 2
.gitignore

@@ -10,5 +10,4 @@
 /.idea/.gitignore
 /.idea/.gitignore
 /.idea/misc.xml
 /.idea/misc.xml
 /.idea/modules.xml
 /.idea/modules.xml
-/work
-/work2
+/work/

+ 3 - 1
src/metadocker/__main__.py

@@ -1,6 +1,8 @@
 from metadocker.cmdline.base import MainArgs
 from metadocker.cmdline.base import MainArgs
+from metadocker.env.base import Env
+
 
 
 def main():
 def main():
-    MainArgs.main(None)
+    MainArgs.main()
 
 
 main()
 main()

+ 15 - 6
src/metadocker/cmdline/base.py

@@ -1,5 +1,6 @@
 import argparse
 import argparse
-from metadocker.cmdline.commands.specific import start, stop, build, run, rm, status, logs, systemd
+from metadocker.cmdline.commands.specific import start, stop, build, run, \
+    rm, status, logs, systemd, shell
 from metadocker.cmdline.commands.main import init, list
 from metadocker.cmdline.commands.main import init, list
 import sys
 import sys
 
 
@@ -11,6 +12,7 @@ class Args(argparse.ArgumentParser):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
         self.subparsers = self.add_subparsers(prog="action", parser_class=argparse.ArgumentParser)
         self.subparsers = self.add_subparsers(prog="action", parser_class=argparse.ArgumentParser)
+        self.add_argument("--debug", "-d", help="Mode débug", action="store_true")
         for k in self._COMMANDS:
         for k in self._COMMANDS:
             self._add_command(k)
             self._add_command(k)
 
 
@@ -19,7 +21,11 @@ class Args(argparse.ArgumentParser):
 
 
         ret = self.parse_args(args)
         ret = self.parse_args(args)
         if hasattr(ret, "command"):
         if hasattr(ret, "command"):
-            ret.command(ret).start(env)
+            if env is not None:
+                ret.command(ret).start(env)
+            else:
+                ret.command(ret).start()
+
         else:
         else:
             self.print_help()
             self.print_help()
             exit(-1)
             exit(-1)
@@ -39,17 +45,20 @@ class Args(argparse.ArgumentParser):
         return parser
         return parser
 
 
     @classmethod
     @classmethod
-    def main(cls, env, args=None):
+    def main(cls, env=None, args=None):
         return cls().parse(env, args)
         return cls().parse(env, args)
 
 
 
 
 class ArgsFromFile(Args):
 class ArgsFromFile(Args):
     _COMMANDS = [build.BuildCommand, start.StartCommand, stop.StopCommand, run.RunCommand,
     _COMMANDS = [build.BuildCommand, start.StartCommand, stop.StopCommand, run.RunCommand,
-                 rm.RmCommand, status.StatusCommand, logs.LogsCommand, systemd.SystemdCommand]
+                 rm.RmCommand, status.StatusCommand, logs.LogsCommand, systemd.SystemdCommand,
+                 shell.ShellCommand]
 
 
     def __init__(self):
     def __init__(self):
         super().__init__()
         super().__init__()
-        self.add_argument("--debug", "-d", help="Mode débug", action="store_true")
 
 
 class MainArgs(Args):
 class MainArgs(Args):
-    _COMMANDS = [init.InitCommand, list.ListCommand]
+    _COMMANDS = [init.InitCommand, list.ListCommand]
+
+    def __init__(self):
+        super().__init__()

+ 13 - 4
src/metadocker/cmdline/commands/base.py

@@ -24,19 +24,28 @@ class Command:
         for k, v in data.items():
         for k, v in data.items():
             setattr(self, k, v)
             setattr(self, k, v)
 
 
-    def start(self, env):
-        env.debug = self.debug
+    def start(self):
         try:
         try:
-            self.run(env)
+            self.run()
         except MetadockerError as err:
         except MetadockerError as err:
             print("Erreur", file=sys.stderr)
             print("Erreur", file=sys.stderr)
             print(err.get_error(), file=sys.stderr)
             print(err.get_error(), file=sys.stderr)
 
 
-    def run(self, env):
+    def run(self):
         raise NotImplementedError()
         raise NotImplementedError()
 
 
 
 
+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)
+
 
 
 
 

+ 1 - 1
src/metadocker/cmdline/commands/main/init.py

@@ -13,7 +13,7 @@ class InitCommand(Command):
         Argument("--output", "-o", action="store_true", help="Le fichier destination", default="./docker")
         Argument("--output", "-o", action="store_true", help="Le fichier destination", default="./docker")
     ]
     ]
 
 
-    def run(self, env):
+    def run(self):
         env_name = self.env.lower()
         env_name = self.env.lower()
         envs = load_env()
         envs = load_env()
         if env_name not in envs:
         if env_name not in envs:

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

@@ -8,7 +8,7 @@ class ListCommand(Command):
     ARGUMENTS = [
     ARGUMENTS = [
     ]
     ]
 
 
-    def run(self, env):
+    def run(self):
         print(f"Environnement disponibles:")
         print(f"Environnement disponibles:")
         for v in load_env().values():
         for v in load_env().values():
             print(v._name_)
             print(v._name_)

+ 10 - 7
src/metadocker/cmdline/commands/specific/build.py

@@ -1,9 +1,10 @@
-from metadocker.cmdline.commands.base import Command, Argument
-from metadocker.docker import DockerBuilder
+import tempfile
+from pathlib import Path
+
+from metadocker.cmdline.commands.base import SpecificCommand, Argument
 from metadocker.docker.driver import driver
 from metadocker.docker.driver import driver
-import os, sys
 
 
-class BuildCommand(Command):
+class BuildCommand(SpecificCommand):
     NAME = "build"
     NAME = "build"
     HELP = "Crée le conteneur"
     HELP = "Crée le conteneur"
     ARGUMENTS = [
     ARGUMENTS = [
@@ -12,9 +13,11 @@ class BuildCommand(Command):
     ]
     ]
 
 
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder()
-        env.build_docker_file(docker, self.dockerfile)
+        if self.dockerfile is None:
+            self.dockerfile = env.root / "Dockerfile"
+
+        env.build_docker_file(self.dockerfile)
         if not self.dry_run:
         if not self.dry_run:
-            driver.build(self.dockerfile, tag=docker.tag)
+            driver.build(self.dockerfile, tag=env.docker.tag)
 
 
 
 

+ 6 - 9
src/metadocker/cmdline/commands/specific/logs.py

@@ -1,10 +1,9 @@
-from metadocker.cmdline.commands.base import Command, Argument
+from metadocker.cmdline.commands.base import SpecificCommand, Argument
 from metadocker.common.errors import SimpleError
 from metadocker.common.errors import SimpleError
-from metadocker.docker import DockerBuilder
 from metadocker.docker.driver import driver
 from metadocker.docker.driver import driver
 
 
 
 
-class LogsCommand(Command):
+class LogsCommand(SpecificCommand):
     NAME = "logs"
     NAME = "logs"
     HELP = "Accede au log du container"
     HELP = "Accede au log du container"
     ARGUMENTS = [
     ARGUMENTS = [
@@ -16,12 +15,10 @@ class LogsCommand(Command):
         Argument("--timestamps", "-t", action="store_true", help='Affiche les logs jusque (e.g. "2013-01-02T13:23:37Z") or relative (e.g. "42m" for 42 minutes)'),
         Argument("--timestamps", "-t", action="store_true", help='Affiche les logs jusque (e.g. "2013-01-02T13:23:37Z") or relative (e.g. "42m" for 42 minutes)'),
     ]
     ]
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder(env)
-        env = env.get_env()
-        name = docker.name
+        name = env.docker.name
 
 
-        for res in driver.ps(name=docker.name):
-            driver.logs(docker.name,
+        for res in driver.ps(name=env.docker.name):
+            driver.logs(env.docker.name,
                         details=self.details,
                         details=self.details,
                         follow=self.follow,
                         follow=self.follow,
                         since=self.since,
                         since=self.since,
@@ -31,4 +28,4 @@ class LogsCommand(Command):
                         catch=False)
                         catch=False)
             break
             break
         else:
         else:
-            raise SimpleError(f"Le conteneur '{docker.name}' n'est disponible")
+            raise SimpleError(f"Le conteneur '{env.docker.name}' n'est disponible")

+ 2 - 2
src/metadocker/cmdline/commands/specific/rm.py

@@ -1,7 +1,7 @@
-from metadocker.cmdline.commands.base import Argument, Command
+from metadocker.cmdline.commands.base import Argument, SpecificCommand
 
 
 
 
-class RmCommand(Command):
+class RmCommand(SpecificCommand):
     NAME = "rm"
     NAME = "rm"
     HELP = "Supprime le conteneur"
     HELP = "Supprime le conteneur"
     ARGUMENTS = [
     ARGUMENTS = [

+ 9 - 11
src/metadocker/cmdline/commands/specific/run.py

@@ -1,13 +1,13 @@
 import sys
 import sys
 from pathlib import Path
 from pathlib import Path
 
 
-from metadocker.cmdline.commands.base import Command, Argument
+from metadocker.cmdline.commands.base import SpecificCommand, Argument
 from metadocker.common.errors import SimpleError
 from metadocker.common.errors import SimpleError
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
 from metadocker.docker.driver import driver
 from metadocker.docker.driver import driver
 
 
 
 
-class RunCommand(Command):
+class RunCommand(SpecificCommand):
     NAME = "run"
     NAME = "run"
     HELP = "Initialise le conteneur"
     HELP = "Initialise le conteneur"
     ARGUMENTS = [
     ARGUMENTS = [
@@ -16,16 +16,14 @@ class RunCommand(Command):
         Argument("commande", nargs="*", help="Command à lancer")
         Argument("commande", nargs="*", help="Command à lancer")
     ]
     ]
 
 
-    def _rm_if_needed(self, env, docker):
-        for data in driver.ps(name=docker.name):
+    def _rm_if_needed(self, env):
+        for data in driver.ps(name=env.docker.name):
             if data.running:
             if data.running:
-                raise SimpleError(f"Erreur le conteneur '{docker.name}' est déja lancé")
-            driver.rm(docker.name)
+                raise SimpleError(f"Erreur le conteneur '{env.docker.name}' est déja lancé")
+            driver.rm(env.docker.name)
 
 
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder(env)
-        env = env.get_env()
-        self._rm_if_needed(env, docker)
+        self._rm_if_needed(env)
 
 
         cmdline = ["-i" if self.interactive else "-d",
         cmdline = ["-i" if self.interactive else "-d",
                    #"--rm"
                    #"--rm"
@@ -45,13 +43,13 @@ class RunCommand(Command):
                 host = f"./{host}"
                 host = f"./{host}"
             cmdline.extend(("-v", f"{host}:{container}"))
             cmdline.extend(("-v", f"{host}:{container}"))
 
 
-        cmdline.extend(("--name", docker.name, "-t", docker.tag))
+        cmdline.extend(("--name", env.docker.name, "-t", env.docker.tag))
         cmdline.extend(self.commande)
         cmdline.extend(self.commande)
 
 
         kwargs = {"catch": False}
         kwargs = {"catch": False}
         if self.interactive:
         if self.interactive:
             kwargs["stdin"]=sys.stdin
             kwargs["stdin"]=sys.stdin
-        data = driver.ps(name=docker.name)
+        data = driver.ps(name=env.docker.name)
 
 
 
 
         driver.run(*cmdline, **kwargs)
         driver.run(*cmdline, **kwargs)

+ 17 - 0
src/metadocker/cmdline/commands/specific/shell.py

@@ -0,0 +1,17 @@
+from metadocker.cmdline.commands.base import Argument, SpecificCommand
+from metadocker.common.errors import SimpleError
+from metadocker.docker.driver import driver
+
+
+class ShellCommand(SpecificCommand):
+    NAME = "shell"
+    HELP = "Lance un shell sur le contaeneur"
+    ARGUMENTS = [
+        Argument("shell", nargs="?", help="Le shell a executer", default="/bin/bash")
+    ]
+
+    def run(self, env):
+        if not driver.is_running(name=env.docker.name):
+            raise SimpleError(f"Le conteneur '{env.docker.name}' n'est pas lancé")
+        env.docker.exec(env.docker.name, self.shell, interactive=True, catch=False)
+

+ 2 - 3
src/metadocker/cmdline/commands/specific/start.py

@@ -1,12 +1,11 @@
-from metadocker.cmdline.commands.base import Command
+from metadocker.cmdline.commands.base import SpecificCommand
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
 from metadocker.docker.driver import driver
 from metadocker.docker.driver import driver
 
 
 
 
-class StartCommand(Command):
+class StartCommand(SpecificCommand):
     NAME = "start"
     NAME = "start"
     HELP = "Lance le conteneur"
     HELP = "Lance le conteneur"
 
 
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder(env)
         driver.start("-a")
         driver.start("-a")

+ 3 - 5
src/metadocker/cmdline/commands/specific/status.py

@@ -1,19 +1,17 @@
 import json
 import json
 
 
-from metadocker.cmdline.commands.base import Command, Argument
+from metadocker.cmdline.commands.base import SpecificCommand, Argument
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
 from metadocker.docker.driver import driver
 from metadocker.docker.driver import driver
 
 
 
 
-class StatusCommand(Command):
+class StatusCommand(SpecificCommand):
     NAME = "status"
     NAME = "status"
     HELP = "Stop le conteneur"
     HELP = "Stop le conteneur"
     ARGUMENTS = [
     ARGUMENTS = [
     ]
     ]
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder(env)
-        env = env.get_env()
-        name = docker.name
+        name = env.docker.name
         data = driver.ps(name=name, classe=dict)
         data = driver.ps(name=name, classe=dict)
         if data:
         if data:
             print(json.dumps(data[0], indent = 2))
             print(json.dumps(data[0], indent = 2))

+ 3 - 5
src/metadocker/cmdline/commands/specific/stop.py

@@ -1,9 +1,9 @@
-from metadocker.cmdline.commands.base import Command, Argument
+from metadocker.cmdline.commands.base import SpecificCommand, Argument
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
 from metadocker.docker.driver import driver
 from metadocker.docker.driver import driver
 
 
 
 
-class StopCommand(Command):
+class StopCommand(SpecificCommand):
     NAME = "stop"
     NAME = "stop"
     HELP = "Stop le conteneur"
     HELP = "Stop le conteneur"
     ARGUMENTS = [
     ARGUMENTS = [
@@ -12,9 +12,7 @@ class StopCommand(Command):
         Argument("--signal", "-s", help="Signal à envoyer au container"),
         Argument("--signal", "-s", help="Signal à envoyer au container"),
     ]
     ]
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder(env)
-        env = env.get_env()
-        name = docker.name
+        name = env.docker.name
         if self.kill:
         if self.kill:
             driver.kill(name, signal=self.signal)
             driver.kill(name, signal=self.signal)
         else:
         else:

+ 4 - 6
src/metadocker/cmdline/commands/specific/systemd.py

@@ -1,9 +1,8 @@
 import subprocess
 import subprocess
 from pathlib import Path
 from pathlib import Path
 
 
-from metadocker.cmdline.commands.base import Command, Argument
+from metadocker.cmdline.commands.base import SpecificCommand, Argument
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
-from metadocker.docker.driver import driver
 
 
 
 
 def get_file_content(name, time=10):
 def get_file_content(name, time=10):
@@ -22,7 +21,7 @@ WantedBy=multi-user.target
 
 
 """
 """
 
 
-class SystemdCommand(Command):
+class SystemdCommand(SpecificCommand):
     NAME = "systemd"
     NAME = "systemd"
     HELP = "Permet de gérer le démarrage automatique"
     HELP = "Permet de gérer le démarrage automatique"
     ARGUMENTS = [
     ARGUMENTS = [
@@ -32,15 +31,14 @@ class SystemdCommand(Command):
 
 
     ]
     ]
     def run(self, env):
     def run(self, env):
-        docker = DockerBuilder(env)
-        name = docker.name
+        name = env.docker.name
         level = 0
         level = 0
         if self.startup:
         if self.startup:
             level = 2
             level = 2
         elif self.install:
         elif self.install:
             level = 1
             level = 1
 
 
-        output = (Path(self.output  or env.root) / f"{name}.service").resolve()
+        output = (Path(self.output or env.root) / f"{name}.service").resolve()
         output.parent.mkdir(exist_ok=True, parents=True)
         output.parent.mkdir(exist_ok=True, parents=True)
         output.write_text(get_file_content(name))
         output.write_text(get_file_content(name))
 
 

+ 4 - 12
src/metadocker/docker/builder.py

@@ -3,20 +3,9 @@ import json
 
 
 class DockerBuilder:
 class DockerBuilder:
 
 
-    def __init__(self, env=None):
+    def __init__(self, project, version, tag=None, name=None):
         self.docker_file = []
         self.docker_file = []
         self.systemd_file = []
         self.systemd_file = []
-        self.project = None
-        self.version = None
-        self.tag = None
-        self.name = None
-        if env:
-            env.init(self)
-
-    def get_file_content(self):
-        return "\n".join(self.docker_file)
-
-    def init(self, project, version, tag=None, name=None):
         self.project = project
         self.project = project
         self.version = version
         self.version = version
         self.tag = tag
         self.tag = tag
@@ -27,6 +16,9 @@ class DockerBuilder:
                 self.tag = f"{self.project.lower()}-latest"
                 self.tag = f"{self.project.lower()}-latest"
         self.name = name if tag else f"{self.project.lower()}"
         self.name = name if tag else f"{self.project.lower()}"
 
 
+    def get_file_content(self):
+        return "\n".join(self.docker_file)
+
     def add(self, *args):
     def add(self, *args):
         self.docker_file.append(" ".join([str(x) for x in args if x is not None]))
         self.docker_file.append(" ".join([str(x) for x in args if x is not None]))
 
 

+ 18 - 0
src/metadocker/docker/driver.py

@@ -54,6 +54,24 @@ class DockerDriver:
     def run(self, *args, **kwarsg):
     def run(self, *args, **kwarsg):
         self._docker("run", *args, **kwarsg)
         self._docker("run", *args, **kwarsg)
 
 
+
+    def is_running(self, name):
+        for data in self.ps(name=name):
+            if data.running:
+                return True
+        return False
+
+
+    def exec(self, name, commande=None, interactive=False, **kwarsg):
+        args = []
+        if interactive:
+            args.append("-i")
+
+        args.append(name)
+        if commande:
+            args.append(commande)
+        self._docker("exec", *args, **kwarsg)
+
     def start(self, *args, **kwarsg):
     def start(self, *args, **kwarsg):
         self._docker("start", *args, **kwarsg)
         self._docker("start", *args, **kwarsg)
 
 

+ 84 - 141
src/metadocker/env/base.py

@@ -7,114 +7,7 @@ from metadocker.cmdline.base import ArgsFromFile
 from metadocker.common.errors import MetadockerError
 from metadocker.common.errors import MetadockerError
 from metadocker.common.misc import get_root_dir
 from metadocker.common.misc import get_root_dir
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
-
-NoneType=None.__class__
-
-def get_types(x):
-    if x is None:
-        return tuple()
-    if not isinstance(x, (list, tuple, set)):
-        return (x,)
-    return x
-
-class Var:
-
-    class VarException(MetadockerError):
-        def get_error(self):
-            raise NotImplementedError()
-
-    class RequiredException(VarException):
-        def __init__(self, name):
-            super().__init__(name)
-            self.name = name
-
-        def get_error(self):
-            return f"La variable {self.name} est requise"
-
-    class ValidationException(VarException):
-        def __init__(self, name, err):
-            super().__init__(name, err)
-            self.name = name
-            self.err = err
-
-        def get_error(self):
-            return f"La variable {self.name} est invalide : {self.err}"
-
-    class TypeException(VarException):
-        def __init__(self, name, types, found):
-            super().__init__(name, types)
-            self.name = name
-
-            if isinstance(types, type):
-                types = [types]
-            types = [x.__name__ for x in types]
-            self.types = tuple(types)
-            self.found = found
-
-        def get_error(self):
-            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=None, required=False, validate=None,
-                 expected_types=None, help=None, is_meta=False, null=False):
-        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
-        self.help = help
-        self.null = null
-        self.is_meta = is_meta
-
-    def validate(self, obj):
-        if not hasattr(obj, self.name):
-            if self.required:
-                raise self.RequiredException(self.name)
-            return self.default_value
-        data = getattr(obj, self.name)
-        if self.expected_types:
-            if data is None and self.null:
-                pass
-            elif not isinstance(data, self.expected_types):
-                raise self.TypeException(self.name, self.expected_types, data)
-
-            data = self._validate(data)
-
-        return data
-
-    def __repr__(self):
-        return f"<{self.__class__.__name__} {self.name}>"
-
-class VarStr(Var):
-    def __init__(self, name, default_value=None, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False):
-        super().__init__(name, default_value, required, validate, help=help,
-                         expected_types=(str, *get_types(expected_types)), is_meta=is_meta, null=null)
-
-class VarPath(Var):
-    def __init__(self, name, default_value=None, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=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)
-
-
-class VarInt(Var):
-    def __init__(self, name, default_value=None, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False):
-        super().__init__(name, default_value, required, validate, help=help, is_meta=is_meta,
-                         expected_types=(int, *get_types(expected_types)), null=null)
-
-class VarNumber(Var):
-    def __init__(self, name, default_value=None, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False):
-        super().__init__(name, default_value, required, validate, help=help, is_meta=is_meta,
-                         expected_types=(float, int, *get_types(expected_types)), null=null)
-
-class VarDict(Var):
-    def __init__(self, name, default_value=None, required=False, validate=None, help=None,
-                 is_meta=False, expected_types=None, null=False):
-        super().__init__(name, default_value, required, validate, help=help, is_meta=is_meta,
-                         expected_types=(dict, *get_types(expected_types)), null=null)
+from metadocker.env.var import VarDict, VarStr, VarPath, NotEmpty, Var, EmptyValue
 
 
 
 
 class EnvHolder:
 class EnvHolder:
@@ -139,8 +32,40 @@ class EnvHolder:
 
 
 class Env:
 class Env:
     _current_env = None
     _current_env = None
-    _vars_ = []
+
+    PROJECT : str
+    PROJECT_VERSION : str
+    DOCKER_TAG : str
+    DOCKER_NAME : str
+    ENV : dict
+    PORTS : dict
+    VOLUMES : dict
+    COPY : dict
+    APP_DIR : Path
+
+    _vars_ = [
+        VarStr("PROJECT", required=True, help="Le nom du projet", is_meta=True, ask=True),
+        VarStr("PROJECT_VERSION", None, help="La version du projet", validate=NotEmpty,
+               is_meta=True, null=True, ask=True),
+        VarStr("DOCKER_TAG", None, is_meta=True, validate=NotEmpty,
+               help="Nom du tag de l'image docker. Par défaut: {env.PROJECT.lower()}-{PROJECT_VERSION}", ask=True),
+
+        VarStr("DOCKER_NAME", None,  is_meta=True, validate=NotEmpty,
+               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,
+                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, help="Le dossier ou est lancé le projet", ask=True),
+
+    ]
     _name_ = None
     _name_ = None
+    _alias_ = []
 
 
 
 
     class EnvException(MetadockerError):
     class EnvException(MetadockerError):
@@ -156,19 +81,35 @@ class Env:
         Env._current_env = self
         Env._current_env = self
         self.root = get_root_dir()
         self.root = get_root_dir()
         self.debug = debug
         self.debug = debug
+        self.meta = None
+        self.docker : DockerBuilder
+        self.env = None
         for k, v in kwargs.items():
         for k, v in kwargs.items():
             setattr(self, k, v)
             setattr(self, k, v)
 
 
-    def check(self):
-        self.get_env()
 
 
-    def get_meta(self):
+    def init(self):
+        self.meta = self.get_meta()
+        self.docker = DockerBuilder(
+            self.meta.PROJECT,
+            self.meta.PROJECT_VERSION,
+            self.meta.DOCKER_TAG,
+            self.meta.DOCKER_NAME,
+        )
+
+    def read(self):
+        self.init()
+        self.env = self.get_env()
+
+    def _validate_fields(self, filter=None):
         data= EnvHolder()
         data= EnvHolder()
         errors = []
         errors = []
         for var in self._vars_:
         for var in self._vars_:
-            if not var.is_meta: continue
+            if filter and not filter(var): continue
             try:
             try:
                 value = var.validate(self)
                 value = var.validate(self)
+            except Var.ByPassException:
+                value = None
             except Var.VarException as err:
             except Var.VarException as err:
                 errors.append(err)
                 errors.append(err)
                 continue
                 continue
@@ -179,50 +120,40 @@ class Env:
 
 
         for k,v in data.items():
         for k,v in data.items():
             setattr(data, k, v)
             setattr(data, k, v)
+            setattr(self, k, v)
         return data
         return data
 
 
 
 
-    def get_env(self):
-        data= EnvHolder()
-        errors = []
-        for var in self._vars_:
-            try:
-                value = var.validate(self)
-            except Var.VarException as err:
-                errors.append(err)
-                continue
-            data[var.name] = value
-
-        if errors:
-            raise self.EnvException(errors)
+    def get_meta(self):
+        return self._validate_fields(lambda x: x.is_meta)
 
 
-        for k,v in data.items():
-            setattr(data, k, v)
-        return data
+    def get_env(self):
+        return self._validate_fields()
 
 
-    def generate_docker_file(self, docker : DockerBuilder, ):
+    def generate_docker_file(self ):
         raise NotImplementedError()
         raise NotImplementedError()
 
 
 
 
-    def init(self, docker : DockerBuilder):
-        raise NotImplementedError()
 
 
+    def build_docker_file(self, output=None):
 
 
-    def build_docker_file(self, docker : DockerBuilder, output=None):
-        self.init(docker)
         if not output:
         if not output:
             temp = tempfile.TemporaryDirectory()
             temp = tempfile.TemporaryDirectory()
             output = Path(temp.name) / "Dockerfile"
             output = Path(temp.name) / "Dockerfile"
         output = Path(output)
         output = Path(output)
         output.parent.mkdir(exist_ok=True, parents=True)
         output.parent.mkdir(exist_ok=True, parents=True)
 
 
-        self.generate_docker_file(docker)
-        output.write_text(docker.get_file_content())
+        self.generate_docker_file()
+        output.write_text(self.docker.get_file_content())
 
 
-    def create_empty_config(self):
-        def _value(v):
+    def create_empty_config(self, interractive=True):
+        def _value(v, field):
+            if v is EmptyValue:
+                return f"# type in ({', '.join([x.__name__ for x in field.expected_types] or ['*'])})"
             if v is None:
             if v is None:
                 return "None"
                 return "None"
+            if isinstance(v, Path):
+                v = str(v)
             if isinstance(v, (str, list, int, float, dict)):
             if isinstance(v, (str, list, int, float, dict)):
                 if v == {}: return "{\n}"
                 if v == {}: return "{\n}"
                 if v == []: return "[\n]"
                 if v == []: return "[\n]"
@@ -232,6 +163,9 @@ class Env:
         def _help(v):
         def _help(v):
             return v.replace("\n", "\n#")
             return v.replace("\n", "\n#")
 
 
+        if interractive:
+            self.ask_for_data()
+
         data = [
         data = [
                 "#!/bin/env python3",
                 "#!/bin/env python3",
                 f"from metadocker.env import {self.__class__.__name__}",
                 f"from metadocker.env import {self.__class__.__name__}",
@@ -241,15 +175,24 @@ class Env:
         for var in self._vars_:
         for var in self._vars_:
             if var.help:
             if var.help:
                 data.append(f"# {var.help}")
                 data.append(f"# {var.help}")
-            if var.required:
-                data.append(f"env.{var.name}={_value(var.default_value)}")
+
+            value = _value(getattr(self, var.name, var.default_value), var)
+            if var.required or var.ask:
+                data.append(f"env.{var.name}={value}")
             else:
             else:
-                data.append(f"# env.{var.name}={_value(var.default_value)}")
+                data.append(f"# env.{var.name}={value}")
             data.append("")
             data.append("")
         data.append("# lancement ")
         data.append("# lancement ")
         data.append("env.main()")
         data.append("env.main()")
         return "\n".join(data)
         return "\n".join(data)
 
 
     def main(self, args=None):
     def main(self, args=None):
+        self.init()
         ArgsFromFile.main(self, args)
         ArgsFromFile.main(self, args)
 
 
+
+    def ask_for_data(self):
+        for var in self._vars_:
+            if var.ask:
+                value = var.ask_value()
+                setattr(self, var.name, value)

+ 80 - 99
src/metadocker/env/pythonserver.py

@@ -1,9 +1,11 @@
 import argparse
 import argparse
 import os
 import os
 import re
 import re
+from pathlib import Path
 
 
 from metadocker.docker import DockerBuilder
 from metadocker.docker import DockerBuilder
-from metadocker.env.base import Env, VarStr, VarInt, VarPath, VarDict
+from metadocker.env.base import Env
+from metadocker.env.var import VarStr, VarInt, VarPath, VarDict
 from metadocker.register import register_env
 from metadocker.register import register_env
 
 
 
 
@@ -11,153 +13,132 @@ from metadocker.register import register_env
 @register_env
 @register_env
 class PythonServerEnv(Env):
 class PythonServerEnv(Env):
     _name_ = "PythonServer"
     _name_ = "PythonServer"
+    _alias_ = ["python"]
     _vars_ = [
     _vars_ = [
+        *Env._vars_,
 
 
-        VarStr("PROJECT", required=True, help="Le nom du projet", is_meta=True),
-        VarStr("PROJECT_VERSION", required=True, help="La version du projet", is_meta=True, null=True),
-
-        VarDict("ENV", {}, required=True, help="La liste des variable d'environnemnt à utiliser"),
-        VarDict("PORTS", {8101:800}, required=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", required=True, help="Le dossier ou est lancé le projet"),
-
+        VarStr("PIP_INSTALL", None, help="La ou les dépendeances pip à installer", ask=True),
+        VarStr("GIT_REPO",  None, help="L'adresse du dépot git", ask=True),
         VarStr("PYTHON_VERSION", "3.11", help="La version de python à utiliser"),
         VarStr("PYTHON_VERSION", "3.11", help="La version de python à utiliser"),
-        VarStr("GIT_REPO", help="L'adresse du dépot git"),
         VarStr("GIT_VERSION", help="La version (branche ou tag) à utiliser. Utiliser None pour rester sur le master", null=True),
         VarStr("GIT_VERSION", help="La version (branche ou tag) à utiliser. Utiliser None pour rester sur le master", null=True),
         VarStr("USER", os.environ["USER"], help="Le nom d'utilisateur à utiliser. L'utilisateur sera crée. Utiliser None pour rester en root"),#
         VarStr("USER", os.environ["USER"], help="Le nom d'utilisateur à utiliser. L'utilisateur sera crée. Utiliser None pour rester en root"),#
         VarInt("UID", os.getuid(), help="L'UID de l'utilisateur, uniquement si USER est défini"), #
         VarInt("UID", os.getuid(), help="L'UID de l'utilisateur, uniquement si USER est défini"), #
         VarPath("BUILD_DIR", "/build", help="Le dossier au sein du container ou est buildé le projet"),
         VarPath("BUILD_DIR", "/build", help="Le dossier au sein du container ou est buildé le projet"),
-
         VarStr("APT_PACKAGES", help="La liste des packages apt à installer (séparé par des espaces)"),
         VarStr("APT_PACKAGES", help="La liste des packages apt à installer (séparé par des espaces)"),
-
         VarStr("RUN_BEFORE_SETUP", help="Une commande a utiliser juste avant de lancer la commande d'installation du module python"),
         VarStr("RUN_BEFORE_SETUP", help="Une commande a utiliser juste avant de lancer la commande d'installation du module python"),
         VarStr("RUN_AFTER_SETUP", help="Une commande a utiliser juste après avoir lancé la commande d'installation du module python"),
         VarStr("RUN_AFTER_SETUP", help="Une commande a utiliser juste après avoir lancé la commande d'installation du module python"),
-        VarStr("INSTALL_CMD", "pip install .", help="La commande à utiliser pour installer le package python"),
-
+        VarStr("GIT_INSTALL_CMD", "pip install .", help="La commande à utiliser pour installer le package python"),
         VarStr("RUN_CMD", help="La commande à installer pour lancer l'application. Par défaut: python -m {PROJECT}"),
         VarStr("RUN_CMD", help="La commande à installer pour lancer l'application. Par défaut: python -m {PROJECT}"),
    ]
    ]
 
 
-
-    def init(self, docker : DockerBuilder):
-        env = self.get_env()
-        meta = self.get_meta()
-        docker.meta(meta)
-        docker.init(
-            env.PROJECT,
-            env.PROJECT_VERSION
-        )
-        return env, meta
-
-    def _get_packages(self, env):
-        data = env.APT_PACKAGES
+    PIP_INSTALL: str
+    GIT_REPO: str
+    PYTHON_VERSION: str
+    GIT_VERSION: str
+    USER: str
+    UID: int
+    BUILD_DIR: Path
+    APT_PACKAGES: str
+    RUN_BEFORE_SETUP: str
+    RUN_AFTER_SETUP: str
+    GIT_INSTALL_CMD: str
+    RUN_CMD: str
+
+
+    def _get_packages(self):
+        data = self.APT_PACKAGES
         packages = []
         packages = []
-        if env.GIT_REPO:
+        if self.GIT_REPO:
             packages.append("git")
             packages.append("git")
 
 
         if isinstance(data, ((tuple, list, set))):
         if isinstance(data, ((tuple, list, set))):
             packages.extend(data)
             packages.extend(data)
-        else:
+        elif isinstance(data, str):
             packages.extend(re.sub("\s\+", " ", data).split(" "))
             packages.extend(re.sub("\s\+", " ", data).split(" "))
         return packages
         return packages
 
 
 
 
-    def _get_envs(self, env):
-        envs=[f'APP_DIR="{env.APP_DIR}"']
-        if env.ENV:
-            for k, v in env.ENV.items():
+    def _get_envs(self):
+        envs=[f'APP_DIR="{self.APP_DIR}"']
+        if self.ENV:
+            for k, v in self.ENV.items():
                 envs.append(f'{k}=\"{v}\"')
                 envs.append(f'{k}=\"{v}\"')
-        for k, v in env.items():
+        for k, v in self.env.items():
             envs.append(f'CFG_{k}=\"{v}\"')
             envs.append(f'CFG_{k}=\"{v}\"')
         return "\\\n    ".join(envs)
         return "\\\n    ".join(envs)
 
 
-    def generate_docker_file(self, docker : DockerBuilder):
-
-        debug = self.debug
-        env, meta = self.init(docker)
+    def generate_docker_file(self):
+        self.docker.from_(f"python:{self.PYTHON_VERSION}-slim")
 
 
+        self.docker.env(self._get_envs())
 
 
-        docker.from_(f"python:{env.PYTHON_VERSION}-slim")
+        if self.COPY:
+            for k, v in self.COPY.items():
+                self.docker.copy(k, v)
 
 
-        docker.env(self._get_envs(env))
+        if self.PORTS:
+            self.docker.expose(*self.PORTS.values())
 
 
-        if env.COPY:
-            for k, v in env.COPY.items():
-                docker.copy(k, v)
-
-        if env.PORTS:
-            docker.expose(*env.PORTS.values())
-
-
-        if env.USER:
-            docker.run(f'adduser --disabled-password --gecos "" --home "/home/{env.USER}"  '
-                           f'--shell "/sbin/nologin"  --uid "{env.UID}"  {env.USER}')
-
-        packages = self._get_packages(env)
+        packages = self._get_packages()
         if packages:
         if packages:
-            docker.run(f'apt update -y && apt install -y', *packages)
+            self.docker.run(f'apt update -y && apt install -y', *packages)
+        self.docker.run("mkdir -p", self.BUILD_DIR, self.APP_DIR)
 
 
-        docker.run("mkdir -p", env.BUILD_DIR, env.APP_DIR)
 
 
-        if env.USER:
-            docker.run("mkdir -p", f"/home/{env.USER}", " ".join(env.VOLUMES.values()))
-            docker.run(f"chown -R {env.USER}:{env.USER}", env.BUILD_DIR, f"/home/{env.USER}",
-                       " ".join(env.VOLUMES.values()), env.APP_DIR)
+        if self.USER:
+            self.docker.run(f'adduser --disabled-password --gecos "" --home "/home/{self.USER}"  '
+                            f'--shell "/sbin/nologin"  --uid "{self.UID}"  {self.USER}')
 
 
-            if debug:
-                docker.run(f"echo 'root:root' | chpasswd")
-                docker.run(f"echo '{env.USER}:root' | chpasswd")
+            self.docker.run("mkdir -p", f"/home/{self.USER}", " ".join(self.VOLUMES.values()))
+            self.docker.run(f"chown -R {self.USER}:{self.USER}", self.BUILD_DIR, f"/home/{self.USER}",
+                       " ".join(self.VOLUMES.values()), self.APP_DIR)
 
 
-            docker.user(env.USER)
-            docker.env("USER", env.USER)
-            docker.env("PATH", f"/home/{env.USER}/.local/bin:$PATH")
+            if self.debug:
+                self.docker.run(f"echo 'root:root' | chpasswd")
+                self.docker.run(f"echo '{self.USER}:root' | chpasswd")
 
 
 
 
-        if env.VOLUMES:
-            docker.volume(" ".join(env.VOLUMES.values()))
+            self.docker.user(self.USER)
+            self.docker.env("USER", self.USER)
+            self.docker.env("PATH", f"/home/{self.USER}/.local/bin:$PATH")
 
 
-        docker.workdir(env.BUILD_DIR)
 
 
-        # docker.run("mkdir -p $HOME/.pip", "&&",
-        #            r"echo '[global]' > $HOME/.pip/pip.conf &&",
-        #            r"echo 'index-url = http://download.zope.org/simple' >> $HOME/.pip/pip.conf &&",
-        #            r"echo 'trusted-host = download.zope.org' >> $HOME/.pip/pip.conf &&",
-        #            r"echo 'extra-index-url= https://data.gautrais.eu/fanch/stable' >> $HOME/.pip/pip.conf")
+        if self.VOLUMES:
+            self.docker.volume(" ".join(self.VOLUMES.values()))
 
 
+        self.docker.workdir(self.BUILD_DIR)
 
 
-        if env.GIT_REPO:
-            if env.GIT_VERSION:
-                docker.run(f"git clone {env.GIT_REPO} . && git checkout {env.GIT_VERSION}")
-            else:
-                docker.run(f"git clone {env.GIT_REPO} . ")
+        self.docker.run("mkdir -p $HOME/.pip", "&&",
+                   r"echo '[global]' > $HOME/.pip/pip.conf &&",
+                   r"echo 'index-url = http://download.zope.org/simple' >> $HOME/.pip/pip.conf &&",
+                   r"echo 'trusted-host = download.zope.org' >> $HOME/.pip/pip.conf &&",
+                   r"echo 'extra-index-url= https://data.gautrais.eu/fanch/stable' >> $HOME/.pip/pip.conf")
 
 
+        # install
+        if self.RUN_BEFORE_SETUP:
+            self.docker.run(self.RUN_BEFORE_SETUP)
 
 
-            if env.RUN_BEFORE_SETUP:
-                docker.run(env.RUN_BEFORE_SETUP)
+        if self.PIP_INSTALL:
+            self.docker.run(f"pip install {self.PIP_INSTALL}")
 
 
-            docker.run("pip install .")
-
-            if env.RUN_AFTER_SETUP:
-                docker.run(env.RUN_AFTER_SETUP)
-        else:
-            if env.RUN_BEFORE_SETUP:
-                docker.run(env.RUN_BEFORE_SETUP)
-
-            if env.PROJECT_VERSION:
-                docker.run(f"pip install {env.PROJECT}=={env.PROJECT_VERSION}")
+        if self.GIT_REPO:
+            if self.GIT_VERSION:
+                self.docker.run(f"git clone {self.GIT_REPO} . && git checkout {self.GIT_VERSION}")
             else:
             else:
-                docker.run(f"pip install {env.PROJECT}")
+                self.docker.run(f"git clone {self.GIT_REPO} . ")
+
+            self.docker.run(self.GIT_INSTALL_CMD)
 
 
-            if env.RUN_AFTER_SETUP:
-                docker.run(env.RUN_AFTER_SETUP)
+        if self.RUN_AFTER_SETUP:
+            self.docker.run(self.RUN_AFTER_SETUP)
 
 
 
 
-        docker.workdir(env.APP_DIR)
+        self.docker.workdir(self.APP_DIR)
 
 
-        if env.RUN_CMD:
-            docker.cmd(env.RUN_CMD)
+        if self.RUN_CMD:
+            self.docker.cmd(self.RUN_CMD)
         else:
         else:
-            docker.run(f"python -m {env.PROJECT} migrate")
-            docker.cmd(f"python -m {env.PROJECT} run")
+            self.docker.run(f"python -m {self.PROJECT} migrate")
+            self.docker.cmd(f"python -m {self.PROJECT} run")
 
 
 
 

+ 178 - 0
src/metadocker/env/var.py

@@ -0,0 +1,178 @@
+from pathlib import Path
+
+from metadocker.common.errors import MetadockerError
+
+NoneType=None.__class__
+
+class Empty: pass
+
+EmptyValue = Empty()
+
+def NotEmpty(x):
+    if not x and x is not None:
+        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
+
+class EmptyObject:
+    def __init__(self, **kwargs):
+        for k, v in kwargs.items():
+            setattr(self, k, v)
+
+class Var:
+
+    class VarException(MetadockerError):
+        def get_error(self):
+            raise NotImplementedError()
+
+    class RequiredException(VarException):
+        def __init__(self, name):
+            super().__init__(name)
+            self.name = name
+
+        def get_error(self):
+            return f"La variable {self.name} est requise"
+
+    class ByPassException(Exception):
+        pass
+
+    class ValidationException(VarException):
+        def __init__(self, name, err):
+            super().__init__(name, err)
+            self.name = name
+            self.err = err
+
+        def get_error(self):
+            return f"La variable {self.name} est invalide : {self.err}"
+
+    class TypeException(VarException):
+        def __init__(self, name, types, found):
+            super().__init__(name, types)
+            self.name = name
+
+            if isinstance(types, type):
+                types = [types]
+            types = [x.__name__ for x in types]
+            self.types = tuple(types)
+            self.found = found
+
+        def get_error(self):
+            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):
+        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
+        self.help = help
+        self.null = null
+        self.is_meta = is_meta
+        self.ask = ask
+
+    @property
+    def has_default(self):
+        return self.default_value is not EmptyValue
+
+    def validate(self, obj):
+        if not hasattr(obj, self.name):
+            if self.default_value is EmptyValue:
+                if self.required:
+                    raise self.RequiredException(self.name)
+                else:
+                    raise self.ByPassException()
+            return self.default_value
+
+        data = getattr(obj, self.name)
+
+        if self.expected_types:
+            if data is None and self.null:
+                pass
+            elif not isinstance(data, self.expected_types):
+                raise self.TypeException(self.name, self.expected_types, data)
+
+            data = self._validate(data)
+
+        return data
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} {self.name}>"
+
+    def _parse(self, type, value):
+        if self.null and value == "None":
+            return None
+        return type(value)
+
+    def ask_value(self):
+        while True:
+            print(self.help)
+            prompt = [self.name]
+            has_default = self.default_value is not EmptyValue
+
+            if has_default:
+                prompt.append(f" [{self.default_value}]")
+
+            value = input("".join(prompt)+": ")
+
+            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)
+
+            except MetadockerError as err:
+                if isinstance(err, self.ValidationException):
+                    err.name = self.name
+                print(err.get_error())
+
+
+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)
+
+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)
+
+
+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)
+
+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)
+
+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)
+

+ 3 - 0
src/metadocker/register.py

@@ -5,6 +5,9 @@ registered_env = {}
 def register_env(x):
 def register_env(x):
     global  registered_env
     global  registered_env
     registered_env[x._name_.lower()] = x
     registered_env[x._name_.lower()] = x
+    for alias in x._alias_:
+        registered_env[alias.lower()] = x
+
     return x
     return x
 
 
 def load_env():
 def load_env():