From 4d175962351447809cf77145a7a508ba50541910 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Fri, 7 Jun 2024 14:16:42 -0300 Subject: [PATCH 1/5] [REF] Included '$ ' to LOG show/simulate as a command. --- docky/cmd/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docky/cmd/base.py b/docky/cmd/base.py index c7279eb..4949986 100644 --- a/docky/cmd/base.py +++ b/docky/cmd/base.py @@ -18,13 +18,13 @@ class Docky(cli.Application): 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") From 105431a6bac4fe58c59fbb5dd7a87a1f1089c74d Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Fri, 7 Jun 2024 14:45:21 -0300 Subject: [PATCH 2/5] [UPD] Version 9.0.0 update to use 'docker compose' V2, included library python-on-whales to get some Config information from compose.yml files. --- docky/__init__.py | 3 +- docky/cmd/base.py | 4 +-- docky/cmd/kill.py | 4 +-- docky/cmd/run_open.py | 14 +++++++-- docky/common/project.py | 70 ++++++++++++++++++++--------------------- requirements.txt | 10 +++--- 6 files changed, 54 insertions(+), 51 deletions(-) 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 4949986..f128228 100644 --- a/docky/cmd/base.py +++ b/docky/cmd/base.py @@ -13,7 +13,7 @@ class Docky(cli.Application): PROGNAME = "docky" - VERSION = "8.0.0" + VERSION = "9.0.0" SUBCOMMAND_HELPMSG = None def _run(self, cmd, retcode=FG): @@ -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..646909a 100644 --- a/docky/cmd/run_open.py +++ b/docky/cmd/run_open.py @@ -57,9 +57,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") diff --git a/docky/common/project.py b/docky/common/project.py index 7c0c78d..4031cbd 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: + self.project = docker.compose.config(return_json=True) + except: print("No docker-compose found, create one with :") print("$ 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 From 43f768ce1eda99b1d30fdbc56b0b3af4b67b0861 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Fri, 7 Jun 2024 14:48:54 -0300 Subject: [PATCH 3/5] [REF] Changed 'print' for 'logger.error' in the case of Compose File not found. --- docky/common/project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docky/common/project.py b/docky/common/project.py index 4031cbd..c21fc84 100644 --- a/docky/common/project.py +++ b/docky/common/project.py @@ -14,8 +14,8 @@ def __init__(self): try: self.project = docker.compose.config(return_json=True) except: - print("No docker-compose found, create one with :") - print("$ docky init") + logger.error("No docker-compose file found, create one with :") + logger.error("$ docky init") exit(-1) self.name = self.project.get("name") From 4c90fe4134447578e8b494e67faa2a945571dd56 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Fri, 7 Jun 2024 14:56:14 -0300 Subject: [PATCH 4/5] [IMP] Included new command 'docky system' just return a resume of 'docker info' for check errors related to OS or older or newer libraries versions. --- docky/cmd/run_open.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docky/cmd/run_open.py b/docky/cmd/run_open.py index 646909a..562142c 100644 --- a/docky/cmd/run_open.py +++ b/docky/cmd/run_open.py @@ -2,9 +2,12 @@ # @author Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import sys 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): @@ -79,3 +82,27 @@ 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) + +@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) From 1c986e659013bcc0fda8c1f0ea43cad525f3f25e Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Wed, 10 Jul 2024 15:03:30 -0300 Subject: [PATCH 5/5] [REF] Adapted 'docky open' command for Docker Compose V2. --- docky/cmd/run_open.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docky/cmd/run_open.py b/docky/cmd/run_open.py index 562142c..e46bd6d 100644 --- a/docky/cmd/run_open.py +++ b/docky/cmd/run_open.py @@ -3,6 +3,7 @@ # 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, logger @@ -19,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) @@ -81,7 +85,24 @@ 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):