Skip to content

Commit

Permalink
Merge pull request #179 from rtCamp/develop
Browse files Browse the repository at this point in the history
Patch v0.13.4
  • Loading branch information
Xieyt authored May 15, 2024
2 parents 901f0e1 + 62946e0 commit 0a51c1b
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 71 deletions.
44 changes: 18 additions & 26 deletions frappe_manager/docker_wrapper/DockerException.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,34 @@
from typing import List, Optional
from typing import List

from frappe_manager.docker_wrapper.subprocess_output import SubprocessOutput


class DockerException(Exception):
def __init__(
self,
command_launched: List[str],
return_code: int,
stdout: Optional[bytes] = None,
stderr: Optional[bytes] = None,
output: SubprocessOutput,
):
self.docker_command: List[str] = command_launched
self.return_code: int = return_code
if stdout is None:
self.stdout: Optional[str] = None
else:
self.stdout: Optional[str] = stdout.decode()
if stderr is None:
self.stderr: Optional[str] = None
else:
self.stderr: Optional[str] = stderr.decode()
self.output = output

command_launched_str = " ".join(command_launched)

error_msg = (
f"The docker command executed was `{command_launched_str}`.\n"
f"It returned with code {return_code}\n"
f"It returned with code {self.output.exit_code}\n"
)
if stdout is not None:
error_msg += f"The content of stdout is '{self.stdout}'\n"

if self.output.stdout:
error_msg += f"The content of stdout is '{self.output.stdout}'\n"
else:
error_msg += (
"The content of stdout can be found above the "
"stacktrace (it wasn't captured).\n"
)
if stderr is not None:
error_msg += f"The content of stderr is '{self.stderr}'\n"
error_msg += "The content of stdout can be found above the " "stacktrace (it wasn't captured).\n"

if self.output.stderr:
error_msg += f"The content of stderr is '{self.output.stderr}'\n"
else:
error_msg += (
"The content of stderr can be found above the "
"stacktrace (it wasn't captured)."
)
error_msg += "The content of stderr can be found above the " "stacktrace (it wasn't captured)."

super().__init__(error_msg)


Expand Down
31 changes: 31 additions & 0 deletions frappe_manager/docker_wrapper/subprocess_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from dataclasses import dataclass
from typing import List


@dataclass
class SubprocessOutput:
stdout: List[str]
stderr: List[str]
combined: List[str]
exit_code: int

@classmethod
def from_output(cls, output):
stdout = []
stderr = []
combined = []
exit_code = 0

for source, line in output:
line = line.decode()
if source == 'exit_code':
exit_code = int(line)
else:
combined.append(line)
if source == 'stdout':
stdout.append(line)
if source == 'stderr':
stderr.append(line)

data = {'stdout': stdout, 'stderr': stderr, 'combined': combined, 'exit_code': exit_code}
return cls(**data)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from frappe_manager.display_manager.DisplayManager import richprint
from pydantic import BaseModel

from frappe_manager.utils.docker import SubprocessOutput
from frappe_manager.docker_wrapper.subprocess_output import SubprocessOutput


# TODO this class will be used for validation for main config
Expand Down
3 changes: 1 addition & 2 deletions frappe_manager/services_manager/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ def init(self):
self.fm_headers_path: Path = self.proxy_manager.dirs.confd.host / 'fm_headers.conf'
self.set_frappe_headers_conf()


def set_frappe_headers_conf(self):
if self.fm_headers_path.parent.exists():
template_path: Path = get_template_path('fm_headers.conf.tmpl')
Expand Down Expand Up @@ -245,7 +244,7 @@ def shell(self, container: str, user: str | None = None):
else:
self.compose_project.docker.compose.exec(container, command=shell_path, capture_output=False)
except DockerException as e:
richprint.warning(f"Shell exited with error code: {e.return_code}")
richprint.warning(f"Shell exited with error code: {e.output.exit_code}")

def remove_itself(self):
shutil.rmtree(self.path)
Expand Down
8 changes: 5 additions & 3 deletions frappe_manager/site_manager/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ def shell(self, compose_service: str, user: str | None):
try:
self.compose_project.docker.compose.exec(**exec_args)
except DockerException as e:
richprint.warning(f"Shell exited with error code: {e.return_code}")
richprint.warning(f"Shell exited with error code: {e.output.exit_code}")

def get_log_file_paths(self):
base_log_dir = self.path / "workspace" / "frappe-bench" / "logs"
Expand Down Expand Up @@ -1197,9 +1197,11 @@ def is_supervisord_running(self, interval: int = 2, timeout: int = 30):
for i in range(timeout):
try:
status_command = 'supervisorctl -c /opt/user/supervisord.conf status all'
self.frappe_service_run_command(status_command)
output = self.compose_project.docker.compose.exec('frappe', status_command, user='frappe', stream=False)
return True
except BenchException:
except DockerException as e:
if any('frappe-bench' in s for s in e.output.combined):
return True
time.sleep(interval)
continue
return False
49 changes: 11 additions & 38 deletions frappe_manager/utils/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,15 @@
from queue import Queue
from subprocess import PIPE, Popen, run
from threading import Thread
from dataclasses import dataclass
from typing import Dict, Iterable, List, Tuple, Union, Optional
from typing import Dict, Iterable, Tuple, Union, Optional
from frappe_manager.logger import log
from frappe_manager.docker_wrapper.DockerException import DockerException
from frappe_manager.display_manager.DisplayManager import richprint
from frappe_manager.docker_wrapper.subprocess_output import SubprocessOutput

process_opened = []


@dataclass
class SubprocessOutput:
stdout: List[str]
stderr: List[str]
combined: List[str]
exit_code: int

@classmethod
def from_output(cls, output):
stdout = []
stderr = []
combined = []
exit_code = 0

for source, line in output:
line = line.decode()
if source == 'exit_code':
exit_code = int(line)
else:
combined.append(line)
if source == 'stdout':
stdout.append(line)
if source == 'stderr':
stderr.append(line)

data = {'stdout': stdout, 'stderr': stderr, 'combined': combined, 'exit_code': exit_code}
return cls(**data)


def reader(pipe, pipe_name, queue):
"""
Reads lines from a pipe and puts them into a queue.
Expand All @@ -63,7 +34,7 @@ def reader(pipe, pipe_name, queue):

def stream_stdout_and_stderr(
full_cmd: list,
env: Dict[str, str] = None,
env: Optional[Dict[str, str]] = None,
) -> Iterable[Tuple[str, bytes]]:
"""
Executes a command in Docker and streams the stdout and stderr outputs.
Expand Down Expand Up @@ -96,7 +67,7 @@ def stream_stdout_and_stderr(
process_opened.append(process.pid)

q = Queue()
full_stderr = b"" # for the error message

# we use deamon threads to avoid hanging if the user uses ctrl+c
th = Thread(target=reader, args=[process.stdout, "stdout", q])
th.daemon = True
Expand All @@ -105,21 +76,23 @@ def stream_stdout_and_stderr(
th.daemon = True
th.start()

output = []
for _ in range(2):
for source, line in iter(q.get, None):
output.append((source, line))
yield source, line
if source == "stderr":
full_stderr += line

exit_code = process.wait()

logger.debug(f"RETURN CODE: {exit_code}")
logger.debug('- -' * 10)
if exit_code != 0:
raise DockerException(full_cmd, exit_code, stderr=full_stderr)

output.append(('exit_code', str(exit_code).encode()))
yield ("exit_code", str(exit_code).encode())

if exit_code != 0:
raise DockerException(full_cmd, SubprocessOutput.from_output(output))


def run_command_with_exit_code(
full_cmd: list,
Expand All @@ -140,7 +113,7 @@ def run_command_with_exit_code(
run_output = run(full_cmd)
exit_code = run_output.returncode
if exit_code != 0:
raise DockerException(full_cmd, exit_code)
raise DockerException(full_cmd, SubprocessOutput([], [], [], exit_code))
return

stream_output: SubprocessOutput = SubprocessOutput.from_output(stream_stdout_and_stderr(full_cmd))
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "frappe-manager"
version = "0.13.3"
version = "0.13.4"
license = "MIT"
repository = "https://github.com/rtcamp/frappe-manager"
description = "A CLI tool based on Docker Compose to easily manage Frappe based projects. As of now, only suitable for development in local machines running on Mac and Linux based OS."
Expand Down

0 comments on commit 0a51c1b

Please sign in to comment.