diff --git a/docky/__init__.py b/docky/__init__.py index 99ceb0b..6355e02 100644 --- a/docky/__init__.py +++ b/docky/__init__.py @@ -3,4 +3,5 @@ from . import cmd from . import common from .main import Docky -from . import dcpatched +# TODO: Check the command 'docky open' +# from . import dcpatched diff --git a/docky/cmd/base.py b/docky/cmd/base.py index c7279eb..f128228 100644 --- a/docky/cmd/base.py +++ b/docky/cmd/base.py @@ -13,18 +13,18 @@ class Docky(cli.Application): PROGNAME = "docky" - VERSION = "8.0.0" + VERSION = "9.0.0" SUBCOMMAND_HELPMSG = None def _run(self, cmd, retcode=FG): """Run a command in a new process and log it""" - logger.debug(str(cmd).replace("/usr/local/bin/", "")) + logger.debug(str("$ " + str(cmd).rsplit("/")[-1])) return cmd & retcode def _exec(self, cmd, args=[]): """Run a command in the same process and log it this will replace the current process by the cmd""" - logger.debug(cmd + " ".join(args)) + logger.debug(str("$ " + str(cmd).rsplit("/")[-1] + " " + " ".join(args))) os.execvpe(cmd, [cmd] + args, local.env) @cli.switch("--verbose", help="Verbose mode", group="Meta-switches") @@ -44,7 +44,7 @@ def _run(self, *args, **kwargs): def _init_project(self): self.project = Project() - self.compose = local["docker-compose"] + self.compose = local["docker"]["compose"] def main(self, *args, **kwargs): if self._project_specific: diff --git a/docky/cmd/kill.py b/docky/cmd/kill.py index 4786222..4025058 100644 --- a/docky/cmd/kill.py +++ b/docky/cmd/kill.py @@ -3,7 +3,6 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from .base import Docky, DockySub -from compose.parallel import parallel_kill @Docky.subcommand("kill") @@ -13,5 +12,4 @@ class DockyKill(DockySub): def _main(self, *args): # docker compose do not kill the container odoo as is was run # manually, so we implement our own kill - containers = self.project.get_containers() - parallel_kill(containers, {"signal": "SIGKILL"}) + self._run(self.compose["kill", "-s", "SIGKILL"]) diff --git a/docky/cmd/run_open.py b/docky/cmd/run_open.py index ffcc80f..e46bd6d 100644 --- a/docky/cmd/run_open.py +++ b/docky/cmd/run_open.py @@ -2,9 +2,13 @@ # @author Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import sys +import subprocess from plumbum import cli from .base import Docky, DockySub -from ..common.api import raise_error +from ..common.api import raise_error, logger + +from python_on_whales import docker class DockyExec(DockySub): @@ -16,7 +20,10 @@ class DockyExec(DockySub): service = cli.SwitchAttr(["service"]) def _use_specific_user(self, service): - return not self.root and self.project.get_user(service) + user = self.project.get_user(service) + if self.root: + user = "root" + return user def _get_cmd_line(self, optionnal_command_line): user = self._use_specific_user(self.service) @@ -57,9 +64,17 @@ def _main(self, *optionnal_command_line): self._run(self.compose["rm", "-f"]) self.project.display_service_tooltip() self.project.create_volume() - self._exec("docker-compose", [ - "run", "--rm", "--service-ports", "--use-aliases", "-e", "NOGOSU=True", - self.service] + self.cmd) + # Default command + docky_cmd = ["run", "--rm", "--service-ports", "--use-aliases", "-e", "NOGOSU=True", self.service] + self.cmd + + self._exec("docker", ["compose"] + docky_cmd) + + # TODO: Should we use python-on-whales commands? + # Its possible make + # docker.compose.run(self.project.name, and other parameters) + # But until now was not possible make the same command as above, + # if its possible we should consider the option to use it. + # https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/ @Docky.subcommand("open") @@ -70,4 +85,45 @@ class DockyOpen(DockyExec): def _main(self, *optionnal_command_line): super()._main(*optionnal_command_line) - self._exec("dcpatched", ["exec", "-e", "NOGOSU=True", self.service] + self.cmd) + # self._exec("dcpatched", ["exec", "-e", "NOGOSU=True", self.service] + self.cmd) + + # Get Project Name + # Example: docky-odoo-brasil-14 odoo + project_name = self.project.name + "-" + self.project.service + + # Get User + user = self._use_specific_user(self.service) + + # Get Container ID + command = "docker ps -aqf name=" + project_name + # Example of return value + # b'b5db9db21381\n' + # Option text=true return as string instead of bytes and strip remove break line + # TODO: Is there a better way to do it, for example with Plumbum? + container_id = subprocess.check_output(command, shell=True,text=True).strip() + + self._exec("docker", ["exec", "-u", user, "-it", container_id, "/bin/bash"]) + +@Docky.subcommand("system") +class DockySystem(DockyExec): + """ + Check your System Infos: + OS Type, Kernel, OS, Docker, Docker Compose, and Docky versions. + """ + def _main(self): + # Info + infos = docker.system.info() + # OS Type + logger.info("OS Type " + infos.os_type) + # Kernel Version + logger.info("Kernel Version " + infos.kernel_version) + # Operation System + logger.info("OS " + infos.operating_system) + # Python Version + logger.info("Python Version " + sys.version) + # Docker Version + logger.info("Docker Version " + infos.server_version) + # Docker Compose Version + logger.info(docker.compose.version()) + # Docky Version + logger.info("Docky Version " + Docky.VERSION) diff --git a/docky/common/project.py b/docky/common/project.py index 7c0c78d..c21fc84 100644 --- a/docky/common/project.py +++ b/docky/common/project.py @@ -2,10 +2,7 @@ # @author Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import docker -from compose.project import OneOffFilter -from compose.cli import command -from compose.config.errors import ComposeFileNotFound +from python_on_whales import docker from plumbum import local from .api import logger @@ -15,42 +12,44 @@ class Project(object): def __init__(self): try: - self.project = command.project_from_options(".", {}) - except ComposeFileNotFound: - print("No docker-compose found, create one with :") - print("$ docky init") + self.project = docker.compose.config(return_json=True) + except: + logger.error("No docker-compose file found, create one with :") + logger.error("$ docky init") exit(-1) - self.name = self.project.name - self.loaded_config = None + self.name = self.project.get("name") + self.loaded_config = self.project self.service = self._get_main_service(self.project) def _get_main_service(self, project): """main_service has docky.main.service defined in his label.""" - for service in project.services: - labels = service.options.get("labels", {}) + for service in project.get("services"): + labels = project["services"][service].get("labels") # service.labels() do not contain docky.main.service # see also compose.service.merge_labels - if labels.get("docky.main.service", False): - return service.name + if labels: + if labels.get("docky.main.service"): + return service def get_containers(self, service=None): - kwargs = {"one_off": OneOffFilter.include} + kwargs = {} if service: - kwargs["service_names"] = [service] - return self.project.containers(**kwargs) + kwargs["services"] = [service] + return docker.compose.ps(**kwargs) def display_service_tooltip(self): infos = self._get_services_info() - for service in self.project.services: - labels = service.options.get("labels", {}) + for service in self.project.get("services"): + dict_service = self.project["services"].get(service) + labels = dict_service.get("labels", {}) if labels.get("docky.access.help"): # TODO remove after some versions logger.warning( "'docky.access.help' is replaced by 'docky.help'. " "Please update this key in your docker files.") - if infos.get(service.name): + if infos.get(dict_service.get("name")): # some applications provide extra parameters to access resource infos[service.name] += labels.get("docky.url_suffix", "") logger.info(infos[service.name]) @@ -60,28 +59,27 @@ def display_service_tooltip(self): def _get_services_info(self): """ Search IP and Port for each services """ - client = docker.from_env() - services = (x for x in client.containers.list() - if self.project.name in x.attrs["Name"]) infos = {} - for serv in services: + main_service = self._get_main_service(self.project) + for service in self.project.get("services"): + if service != main_service: + continue + serv = self.project["services"][service] proj_key = [ - x for x in serv.attrs["NetworkSettings"]["Networks"].keys() - if self.project.name in x] + x for x in serv["networks"].keys()] proj_key = proj_key and proj_key[0] or False - if not serv.attrs["NetworkSettings"]["Networks"].get(proj_key): + if not serv["networks"]: continue - ip = serv.attrs["NetworkSettings"]["Networks"][proj_key].get( - "IPAddress", "") + ip = serv["networks"].get("IPAdress", "") info = { - "name": serv.attrs["Config"]["Labels"].get( + "name": serv["labels"].get( "com.docker.compose.service", ""), "ip": ip, - "port": [x for x in serv.attrs["NetworkSettings"].get("Ports", "")] + "port": [x for x in serv.get("ports", "")], } if info["name"] != "db" and info.get("port"): urls = ["http://%s:%s" % (info["ip"], port.replace("/tcp", "")) - for port in info["port"]] + for port in info["port"][0]] # There is no web app to access 'db' service: try adminer for that infos[info["name"]] = "%s %s" % (info["name"], " ".join(urls)) return infos @@ -92,9 +90,10 @@ def create_volume(self): Only apply to external volumes. docker-compose up do not attemps to create it so we have to do it ourselves""" - for service in self.project.services: - for volume in service.options.get("volumes", []): - if volume.external: + for service in self.project.get("services"): + dict_service = self.project["services"].get(service) + for volume in dict_service.get("volumes", []): + if volume.get("external"): path = local.path(local.env.expand(volume.external)) if not path.exists(): logger.info( @@ -103,7 +102,6 @@ def create_volume(self): path.mkdir() def get_user(self, service_name): - service = self.project.get_service(name=service_name) - labels = service.options.get("labels") + labels = self.project["services"].get(service_name).get("labels") if labels: - return labels.get("docky.user", None) + return labels.get("docky.user") diff --git a/requirements.txt b/requirements.txt index 6a11ba7..dea9cb1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,7 @@ -docker-compose>=1.23.1 +python-on-whales plumbum rainbow_logging_handler python-slugify -# Only for solving installation issue with pip that fail to -# solve the version of request compatible with docker and docker-compose -requests<3,>=2.20.0 -importlib-metadata; python_version >= '3.10' -PyYAML >= 5.1, < 5.4 +requests +importlib-metadata +PyYAML >= 6.0.1