Coverage for frappe_manager / migration_manager / migration_helpers.py: 64%
104 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
1import json
2import platform
3from pathlib import Path
5from frappe_manager import CLI_SERVICES_DIRECTORY
6from frappe_manager.docker import ComposeFile, DockerClient
7from frappe_manager.migration_manager.migration_constants import MIGRATION_BENCH_STOP_TIMEOUT_SECONDS
8from frappe_manager.output_manager.base import OutputHandler
11class MigrationBench:
12 def __init__(self, name: str, path: Path, output: OutputHandler | None = None) -> None:
13 self.name = name
14 self.path = path
15 self.output = output
16 self.compose_file_manager = ComposeFile(
17 self.path / "docker-compose.yml",
18 "docker-compose.tmpl",
19 )
20 self.docker = DockerClient(compose_file_path=self.compose_file_manager.compose_path, output=output)
22 self.workers_compose_file_manager = ComposeFile(
23 self.path / "docker-compose.workers.yml",
24 "docker-compose.workers.tmpl",
25 )
26 self.workers_docker = DockerClient(
27 compose_file_path=self.workers_compose_file_manager.compose_path,
28 output=output,
29 )
31 @property
32 def compose(self):
33 assert self.docker.compose is not None
34 return self.docker.compose
36 @property
37 def running(self) -> bool:
38 services = self.compose_file_manager.get_services_list()
39 running_status = self.get_services_running_status()
41 if not running_status:
42 return False
44 for service in services:
45 try:
46 if not running_status[service] == "running":
47 return False
48 except KeyError:
49 return False
50 return True
52 @property
53 def workers_running(self) -> bool:
54 from frappe_manager.docker import DockerException
56 services = self.workers_compose_file_manager.get_services_list()
58 try:
59 all_statuses = self.workers_docker.compose.get_all_services_status()
60 containers = self.workers_compose_file_manager.get_container_names().values()
61 running_status = {
62 status["Service"]: status["State"] for status in all_statuses if status.get("Name") in containers
63 }
65 for service in services:
66 if running_status.get(service) != "running":
67 return False
68 return True
69 except (DockerException, KeyError):
70 return False
72 def get_services_running_status(self) -> dict:
73 from frappe_manager.docker import DockerException
75 services = self.compose_file_manager.get_services_list()
76 containers = self.compose_file_manager.get_container_names().values()
78 try:
79 all_statuses = self.compose.get_all_services_status()
80 return {status["Service"]: status["State"] for status in all_statuses if status.get("Name") in containers}
81 except DockerException:
82 return {}
84 def get_db_connection_info(self):
85 from frappe_manager.utils.site import get_bench_db_connection_info
87 return get_bench_db_connection_info(self.name, self.path)
89 def common_bench_config_set(self, config: dict):
90 """
91 Sets the values in the common_site_config.json file.
92 Args:
93 config (dict): A dictionary containing the key-value pairs to be set in the common_site_config.json file.
94 Returns:
95 bool: True if the values are successfully set, False otherwise.
96 """
97 common_bench_config_path = self.path / "workspace/frappe-bench/sites/common_site_config.json"
99 if not common_bench_config_path.exists():
100 return False
102 common_site_config = {}
104 with open(common_bench_config_path) as f:
105 common_site_config = json.load(f)
107 try:
108 for key, value in config.items():
109 common_site_config[key] = value
110 with open(common_bench_config_path, "w") as f:
111 json.dump(common_site_config, f)
112 return True
113 except KeyError as e:
114 return False
117class MigrationBenches:
118 def __init__(self, benches_path: Path) -> None:
119 self.benches_path = benches_path
121 def get_all_benches(self, exclude: list[str] | None = None):
122 exclude = exclude or []
123 benches: dict[str, Path] = {}
124 for dir in self.benches_path.iterdir():
125 if dir.is_dir() and dir.parts[-1] not in exclude:
126 name = dir.parts[-1]
127 dir = dir / "docker-compose.yml"
128 if dir.exists():
129 benches[name] = dir
130 return benches
132 def stop_benches(self, timeout: int = MIGRATION_BENCH_STOP_TIMEOUT_SECONDS):
133 compose_list = self.get_all_benches()
134 for name, compose_path in compose_list.items():
135 bench = MigrationBench(name, compose_path.parent)
136 bench.docker.compose.stop(timeout=timeout, stream=False)
139class MigrationServicesManager:
140 def __init__(self, services_path: Path = CLI_SERVICES_DIRECTORY):
141 self.services_path = services_path
143 template_name = "docker-compose.services.tmpl"
145 if platform.system() == "Darwin":
146 template_name = "docker-compose.services.osx.tmpl"
148 self.compose_file_manager = ComposeFile(
149 self.services_path / "docker-compose.yml",
150 template_name,
151 )
152 self.docker = DockerClient(compose_file_path=self.compose_file_manager.compose_path)
154 @property
155 def compose(self):
156 assert self.docker.compose is not None
157 return self.docker.compose