devservices 1.0.9__tar.gz → 1.0.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devservices-1.0.9 → devservices-1.0.11}/PKG-INFO +1 -1
- {devservices-1.0.9 → devservices-1.0.11}/README.md +1 -1
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/down.py +30 -10
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/list_services.py +16 -3
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/logs.py +7 -2
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/status.py +3 -1
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/up.py +10 -3
- {devservices-1.0.9 → devservices-1.0.11}/devservices/exceptions.py +10 -4
- {devservices-1.0.9 → devservices-1.0.11}/devservices/main.py +1 -1
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/dependencies.py +83 -23
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/docker_compose.py +1 -1
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/state.py +48 -22
- {devservices-1.0.9 → devservices-1.0.11}/devservices.egg-info/PKG-INFO +1 -1
- {devservices-1.0.9 → devservices-1.0.11}/pyproject.toml +1 -1
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_down.py +192 -83
- devservices-1.0.11/tests/commands/test_list_services.py +176 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_logs.py +27 -8
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_purge.py +64 -27
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_up.py +289 -294
- {devservices-1.0.9 → devservices-1.0.11}/tests/utils/test_dependencies.py +619 -6
- devservices-1.0.11/tests/utils/test_state.py +84 -0
- devservices-1.0.9/tests/commands/test_list_services.py +0 -89
- devservices-1.0.9/tests/utils/test_state.py +0 -54
- {devservices-1.0.9 → devservices-1.0.11}/LICENSE.md +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/__init__.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/__init__.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/list_dependencies.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/purge.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/commands/update.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/configs/service_config.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/constants.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/__init__.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/check_for_update.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/console.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/devenv.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/docker.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/file_lock.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/install_binary.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices/utils/services.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices.egg-info/SOURCES.txt +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices.egg-info/requires.txt +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/setup.cfg +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/testing/__init__.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/testing/utils.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/__init__.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_list_dependencies.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_status.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/commands/test_update.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/configs/test_service_config.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/conftest.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/utils/test_check_for_update.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/utils/test_docker.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/utils/test_docker_compose.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/utils/test_install_binary.py +0 -0
- {devservices-1.0.9 → devservices-1.0.11}/tests/utils/test_services.py +0 -0
|
@@ -31,6 +31,7 @@ from devservices.utils.docker_compose import run_cmd
|
|
|
31
31
|
from devservices.utils.services import find_matching_service
|
|
32
32
|
from devservices.utils.services import Service
|
|
33
33
|
from devservices.utils.state import State
|
|
34
|
+
from devservices.utils.state import StateTables
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -75,12 +76,20 @@ def down(args: Namespace) -> None:
|
|
|
75
76
|
modes = service.config.modes
|
|
76
77
|
|
|
77
78
|
state = State()
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
starting_services = set(state.get_service_entries(StateTables.STARTING_SERVICES))
|
|
80
|
+
started_services = set(state.get_service_entries(StateTables.STARTED_SERVICES))
|
|
81
|
+
active_services = starting_services.union(started_services)
|
|
82
|
+
if service.name not in active_services:
|
|
80
83
|
console.warning(f"{service.name} is not running")
|
|
81
84
|
exit(0)
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
active_starting_modes = state.get_active_modes_for_service(
|
|
87
|
+
service.name, StateTables.STARTING_SERVICES
|
|
88
|
+
)
|
|
89
|
+
active_started_modes = state.get_active_modes_for_service(
|
|
90
|
+
service.name, StateTables.STARTED_SERVICES
|
|
91
|
+
)
|
|
92
|
+
active_modes = active_starting_modes or active_started_modes
|
|
84
93
|
mode_dependencies = set()
|
|
85
94
|
for active_mode in active_modes:
|
|
86
95
|
active_mode_dependencies = modes.get(active_mode, [])
|
|
@@ -95,7 +104,9 @@ def down(args: Namespace) -> None:
|
|
|
95
104
|
)
|
|
96
105
|
except DependencyError as de:
|
|
97
106
|
capture_exception(de)
|
|
98
|
-
status.failure(
|
|
107
|
+
status.failure(
|
|
108
|
+
f"{str(de)}. If this error persists, try running `devservices purge`"
|
|
109
|
+
)
|
|
99
110
|
exit(1)
|
|
100
111
|
try:
|
|
101
112
|
remote_dependencies = get_non_shared_remote_dependencies(
|
|
@@ -103,17 +114,26 @@ def down(args: Namespace) -> None:
|
|
|
103
114
|
)
|
|
104
115
|
except DependencyError as de:
|
|
105
116
|
capture_exception(de)
|
|
106
|
-
status.failure(
|
|
117
|
+
status.failure(
|
|
118
|
+
f"{str(de)}. If this error persists, try running `devservices purge`"
|
|
119
|
+
)
|
|
107
120
|
exit(1)
|
|
108
121
|
|
|
109
122
|
# Check if any service depends on the service we are trying to bring down
|
|
110
123
|
# TODO: We should also take into account the active modes of the other services (this is not trivial to do)
|
|
111
|
-
other_started_services =
|
|
124
|
+
other_started_services = active_services.difference({service.name})
|
|
112
125
|
dependent_service_name = None
|
|
113
126
|
for other_started_service in other_started_services:
|
|
114
127
|
other_service = find_matching_service(other_started_service)
|
|
115
|
-
|
|
116
|
-
other_service.name
|
|
128
|
+
other_service_active_starting_modes = state.get_active_modes_for_service(
|
|
129
|
+
other_service.name, StateTables.STARTING_SERVICES
|
|
130
|
+
)
|
|
131
|
+
other_service_active_started_modes = state.get_active_modes_for_service(
|
|
132
|
+
other_service.name, StateTables.STARTED_SERVICES
|
|
133
|
+
)
|
|
134
|
+
other_service_active_modes = (
|
|
135
|
+
other_service_active_starting_modes
|
|
136
|
+
or other_service_active_started_modes
|
|
117
137
|
)
|
|
118
138
|
dependency_graph = construct_dependency_graph(
|
|
119
139
|
other_service, other_service_active_modes
|
|
@@ -137,8 +157,8 @@ def down(args: Namespace) -> None:
|
|
|
137
157
|
)
|
|
138
158
|
|
|
139
159
|
# TODO: We should factor in healthchecks here before marking service as not running
|
|
140
|
-
state
|
|
141
|
-
state.
|
|
160
|
+
state.remove_service_entry(service.name, StateTables.STARTING_SERVICES)
|
|
161
|
+
state.remove_service_entry(service.name, StateTables.STARTED_SERVICES)
|
|
142
162
|
if dependent_service_name is None:
|
|
143
163
|
console.success(f"{service.name} stopped")
|
|
144
164
|
|
|
@@ -8,6 +8,7 @@ from devservices.utils.console import Console
|
|
|
8
8
|
from devservices.utils.devenv import get_coderoot
|
|
9
9
|
from devservices.utils.services import get_local_services
|
|
10
10
|
from devservices.utils.state import State
|
|
11
|
+
from devservices.utils.state import StateTables
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -30,7 +31,9 @@ def list_services(args: Namespace) -> None:
|
|
|
30
31
|
coderoot = get_coderoot()
|
|
31
32
|
services = get_local_services(coderoot)
|
|
32
33
|
state = State()
|
|
33
|
-
|
|
34
|
+
starting_services = set(state.get_service_entries(StateTables.STARTING_SERVICES))
|
|
35
|
+
started_services = set(state.get_service_entries(StateTables.STARTED_SERVICES))
|
|
36
|
+
running_services = starting_services.union(started_services)
|
|
34
37
|
|
|
35
38
|
if not services:
|
|
36
39
|
console.warning("No services found")
|
|
@@ -46,8 +49,18 @@ def list_services(args: Namespace) -> None:
|
|
|
46
49
|
console.info("Running services:")
|
|
47
50
|
|
|
48
51
|
for service in services_to_show:
|
|
49
|
-
status = "
|
|
50
|
-
|
|
52
|
+
status = "stopped"
|
|
53
|
+
if service.name in starting_services:
|
|
54
|
+
status = "starting"
|
|
55
|
+
elif service.name in started_services:
|
|
56
|
+
status = "started"
|
|
57
|
+
active_starting_modes = state.get_active_modes_for_service(
|
|
58
|
+
service.name, StateTables.STARTING_SERVICES
|
|
59
|
+
)
|
|
60
|
+
active_started_modes = state.get_active_modes_for_service(
|
|
61
|
+
service.name, StateTables.STARTED_SERVICES
|
|
62
|
+
)
|
|
63
|
+
active_modes = active_starting_modes or active_started_modes
|
|
51
64
|
console.info(f"- {service.name}")
|
|
52
65
|
console.info(f" modes: {active_modes}")
|
|
53
66
|
console.info(f" status: {status}")
|
|
@@ -28,6 +28,7 @@ from devservices.utils.docker_compose import run_cmd
|
|
|
28
28
|
from devservices.utils.services import find_matching_service
|
|
29
29
|
from devservices.utils.services import Service
|
|
30
30
|
from devservices.utils.state import State
|
|
31
|
+
from devservices.utils.state import StateTables
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -67,7 +68,9 @@ def logs(args: Namespace) -> None:
|
|
|
67
68
|
mode_dependencies = modes[mode_to_use]
|
|
68
69
|
|
|
69
70
|
state = State()
|
|
70
|
-
|
|
71
|
+
starting_services = set(state.get_service_entries(StateTables.STARTING_SERVICES))
|
|
72
|
+
started_services = set(state.get_service_entries(StateTables.STARTED_SERVICES))
|
|
73
|
+
running_services = starting_services.union(started_services)
|
|
71
74
|
if service.name not in running_services:
|
|
72
75
|
console.warning(f"Service {service.name} is not running")
|
|
73
76
|
return
|
|
@@ -76,7 +79,9 @@ def logs(args: Namespace) -> None:
|
|
|
76
79
|
remote_dependencies = install_and_verify_dependencies(service)
|
|
77
80
|
except DependencyError as de:
|
|
78
81
|
capture_exception(de)
|
|
79
|
-
console.failure(
|
|
82
|
+
console.failure(
|
|
83
|
+
f"{str(de)}. If this error persists, try running `devservices purge`"
|
|
84
|
+
)
|
|
80
85
|
exit(1)
|
|
81
86
|
try:
|
|
82
87
|
logs_output = _logs(service, remote_dependencies, mode_dependencies)
|
|
@@ -110,7 +110,9 @@ def status(args: Namespace) -> None:
|
|
|
110
110
|
remote_dependencies = install_and_verify_dependencies(service)
|
|
111
111
|
except DependencyError as de:
|
|
112
112
|
capture_exception(de)
|
|
113
|
-
console.failure(
|
|
113
|
+
console.failure(
|
|
114
|
+
f"{str(de)}. If this error persists, try running `devservices purge`"
|
|
115
|
+
)
|
|
114
116
|
exit(1)
|
|
115
117
|
try:
|
|
116
118
|
status_json_results = _status(service, remote_dependencies, mode_dependencies)
|
|
@@ -34,6 +34,7 @@ from devservices.utils.docker_compose import run_cmd
|
|
|
34
34
|
from devservices.utils.services import find_matching_service
|
|
35
35
|
from devservices.utils.services import Service
|
|
36
36
|
from devservices.utils.state import State
|
|
37
|
+
from devservices.utils.state import StateTables
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -78,6 +79,8 @@ def up(args: Namespace) -> None:
|
|
|
78
79
|
modes = service.config.modes
|
|
79
80
|
mode = args.mode
|
|
80
81
|
|
|
82
|
+
state = State()
|
|
83
|
+
|
|
81
84
|
with Status(
|
|
82
85
|
lambda: console.warning(f"Starting '{service.name}' in mode: '{mode}'"),
|
|
83
86
|
lambda: console.success(f"{service.name} started"),
|
|
@@ -89,7 +92,9 @@ def up(args: Namespace) -> None:
|
|
|
89
92
|
)
|
|
90
93
|
except DependencyError as de:
|
|
91
94
|
capture_exception(de)
|
|
92
|
-
status.failure(
|
|
95
|
+
status.failure(
|
|
96
|
+
f"{str(de)}. If this error persists, try running `devservices purge`"
|
|
97
|
+
)
|
|
93
98
|
exit(1)
|
|
94
99
|
except ModeDoesNotExistError as mde:
|
|
95
100
|
status.failure(str(mde))
|
|
@@ -99,6 +104,8 @@ def up(args: Namespace) -> None:
|
|
|
99
104
|
except subprocess.CalledProcessError:
|
|
100
105
|
# Network already exists, ignore the error
|
|
101
106
|
pass
|
|
107
|
+
# Add the service to the starting services table
|
|
108
|
+
state.update_service_entry(service.name, mode, StateTables.STARTING_SERVICES)
|
|
102
109
|
try:
|
|
103
110
|
mode_dependencies = modes[mode]
|
|
104
111
|
_up(service, [mode], remote_dependencies, mode_dependencies, status)
|
|
@@ -107,8 +114,8 @@ def up(args: Namespace) -> None:
|
|
|
107
114
|
status.failure(f"Failed to start {service.name}: {dce.stderr}")
|
|
108
115
|
exit(1)
|
|
109
116
|
# TODO: We should factor in healthchecks here before marking service as running
|
|
110
|
-
state
|
|
111
|
-
state.
|
|
117
|
+
state.remove_service_entry(service.name, StateTables.STARTING_SERVICES)
|
|
118
|
+
state.update_service_entry(service.name, mode, StateTables.STARTED_SERVICES)
|
|
112
119
|
|
|
113
120
|
|
|
114
121
|
def _bring_up_dependency(
|
|
@@ -70,27 +70,33 @@ class DockerError(Exception):
|
|
|
70
70
|
class DockerComposeError(DockerError):
|
|
71
71
|
"""Base class for Docker Compose related errors."""
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
def __str__(self) -> str:
|
|
74
|
+
return f"DockerComposeError: {self.command} returned {self.returncode} error: {self.stderr}"
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
class ModeDoesNotExistError(Exception):
|
|
77
78
|
"""Raised when a mode does not exist."""
|
|
78
79
|
|
|
79
|
-
def __init__(self, service_name: str, mode: str):
|
|
80
|
+
def __init__(self, service_name: str, mode: str, available_modes: list[str]):
|
|
80
81
|
self.service_name = service_name
|
|
81
82
|
self.mode = mode
|
|
83
|
+
self.available_modes = available_modes
|
|
82
84
|
|
|
83
85
|
def __str__(self) -> str:
|
|
84
|
-
|
|
86
|
+
# All valid services should have at least one mode, so we don't check for an empty list
|
|
87
|
+
return f"ModeDoesNotExistError: Mode '{self.mode}' does not exist for service '{self.service_name}'.\nAvailable modes: {', '.join(self.available_modes)}"
|
|
85
88
|
|
|
86
89
|
|
|
87
90
|
class DependencyError(Exception):
|
|
88
91
|
"""Base class for dependency-related errors."""
|
|
89
92
|
|
|
90
|
-
def __init__(
|
|
93
|
+
def __init__(
|
|
94
|
+
self, repo_name: str, repo_link: str, branch: str, stderr: str | None = None
|
|
95
|
+
):
|
|
91
96
|
self.repo_name = repo_name
|
|
92
97
|
self.repo_link = repo_link
|
|
93
98
|
self.branch = branch
|
|
99
|
+
self.stderr = stderr
|
|
94
100
|
|
|
95
101
|
def __str__(self) -> str:
|
|
96
102
|
return f"DependencyError: {self.repo_name} ({self.repo_link}) on {self.branch}"
|
|
@@ -33,7 +33,7 @@ sentry_environment = (
|
|
|
33
33
|
"development" if os.environ.get("IS_DEV", default=False) else "production"
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
disable_sentry = os.environ.get("
|
|
36
|
+
disable_sentry = os.environ.get("DEVSERVICES_DISABLE_SENTRY", default=False)
|
|
37
37
|
logging.basicConfig(level=logging.INFO)
|
|
38
38
|
|
|
39
39
|
if not disable_sentry:
|
|
@@ -13,6 +13,7 @@ from dataclasses import dataclass
|
|
|
13
13
|
from typing import TextIO
|
|
14
14
|
from typing import TypeGuard
|
|
15
15
|
|
|
16
|
+
from sentry_sdk import capture_message
|
|
16
17
|
from sentry_sdk import set_context
|
|
17
18
|
|
|
18
19
|
from devservices.configs.service_config import Dependency
|
|
@@ -38,6 +39,7 @@ from devservices.utils.file_lock import lock
|
|
|
38
39
|
from devservices.utils.services import find_matching_service
|
|
39
40
|
from devservices.utils.services import Service
|
|
40
41
|
from devservices.utils.state import State
|
|
42
|
+
from devservices.utils.state import StateTables
|
|
41
43
|
|
|
42
44
|
RELEVANT_GIT_CONFIG_KEYS = [
|
|
43
45
|
"init.defaultbranch",
|
|
@@ -208,7 +210,11 @@ def install_and_verify_dependencies(
|
|
|
208
210
|
mode_dependencies = set()
|
|
209
211
|
for mode in modes:
|
|
210
212
|
if mode not in service.config.modes:
|
|
211
|
-
raise ModeDoesNotExistError(
|
|
213
|
+
raise ModeDoesNotExistError(
|
|
214
|
+
service_name=service.name,
|
|
215
|
+
mode=mode,
|
|
216
|
+
available_modes=list(service.config.modes.keys()),
|
|
217
|
+
)
|
|
212
218
|
mode_dependencies.update(service.config.modes[mode])
|
|
213
219
|
matching_dependencies = [
|
|
214
220
|
dependency
|
|
@@ -261,12 +267,14 @@ def get_non_shared_remote_dependencies(
|
|
|
261
267
|
service_to_stop: Service, remote_dependencies: set[InstalledRemoteDependency]
|
|
262
268
|
) -> set[InstalledRemoteDependency]:
|
|
263
269
|
state = State()
|
|
264
|
-
|
|
270
|
+
starting_services = set(state.get_service_entries(StateTables.STARTING_SERVICES))
|
|
271
|
+
started_services = set(state.get_service_entries(StateTables.STARTED_SERVICES))
|
|
272
|
+
active_services = starting_services.union(started_services)
|
|
265
273
|
# We don't care about the remote dependencies of the service we are stopping
|
|
266
|
-
|
|
274
|
+
active_services.remove(service_to_stop.name)
|
|
267
275
|
other_running_remote_dependencies: set[InstalledRemoteDependency] = set()
|
|
268
276
|
base_running_service_names: set[str] = set()
|
|
269
|
-
for started_service_name in
|
|
277
|
+
for started_service_name in active_services:
|
|
270
278
|
started_service = find_matching_service(started_service_name)
|
|
271
279
|
for dependency_name in service_to_stop.config.dependencies.keys():
|
|
272
280
|
if dependency_name == started_service.config.service_name:
|
|
@@ -408,6 +416,7 @@ def install_dependency(dependency: RemoteConfig) -> set[InstalledRemoteDependenc
|
|
|
408
416
|
raise ModeDoesNotExistError(
|
|
409
417
|
service_name=installed_config.service_name,
|
|
410
418
|
mode=dependency.mode,
|
|
419
|
+
available_modes=list(installed_config.modes.keys()),
|
|
411
420
|
)
|
|
412
421
|
|
|
413
422
|
active_nested_dependencies = [
|
|
@@ -473,27 +482,49 @@ def _update_dependency(
|
|
|
473
482
|
repo_name=dependency.repo_name,
|
|
474
483
|
repo_link=dependency.repo_link,
|
|
475
484
|
branch=dependency.branch,
|
|
485
|
+
stderr=e.stderr,
|
|
476
486
|
) from e
|
|
477
487
|
|
|
478
488
|
# Check if the local repo is up-to-date
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
489
|
+
try:
|
|
490
|
+
local_commit = _rev_parse(dependency_repo_dir, "HEAD")
|
|
491
|
+
except subprocess.CalledProcessError as e:
|
|
492
|
+
raise DependencyError(
|
|
493
|
+
repo_name=dependency.repo_name,
|
|
494
|
+
repo_link=dependency.repo_link,
|
|
495
|
+
branch=dependency.branch,
|
|
496
|
+
stderr=e.stderr,
|
|
497
|
+
) from e
|
|
498
|
+
|
|
499
|
+
try:
|
|
500
|
+
remote_commit = _rev_parse(dependency_repo_dir, "FETCH_HEAD")
|
|
501
|
+
except subprocess.CalledProcessError as e:
|
|
502
|
+
raise DependencyError(
|
|
503
|
+
repo_name=dependency.repo_name,
|
|
504
|
+
repo_link=dependency.repo_link,
|
|
505
|
+
branch=dependency.branch,
|
|
506
|
+
stderr=e.stderr,
|
|
507
|
+
) from e
|
|
490
508
|
|
|
491
509
|
if local_commit == remote_commit:
|
|
492
510
|
# Already up-to-date, don't pull anything
|
|
511
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
512
|
+
logger.debug(
|
|
513
|
+
"Dependency %s is already up-to-date, not pulling anything",
|
|
514
|
+
dependency.repo_name,
|
|
515
|
+
)
|
|
493
516
|
return
|
|
494
517
|
|
|
495
518
|
# If it's not up-to-date, checkout the latest changes (forcibly)
|
|
496
|
-
|
|
519
|
+
try:
|
|
520
|
+
_run_command(["git", "checkout", "-f", "FETCH_HEAD"], cwd=dependency_repo_dir)
|
|
521
|
+
except subprocess.CalledProcessError as e:
|
|
522
|
+
raise DependencyError(
|
|
523
|
+
repo_name=dependency.repo_name,
|
|
524
|
+
repo_link=dependency.repo_link,
|
|
525
|
+
branch=dependency.branch,
|
|
526
|
+
stderr=e.stderr,
|
|
527
|
+
) from e
|
|
497
528
|
|
|
498
529
|
|
|
499
530
|
def _checkout_dependency(
|
|
@@ -518,6 +549,7 @@ def _checkout_dependency(
|
|
|
518
549
|
repo_name=dependency.repo_name,
|
|
519
550
|
repo_link=dependency.repo_link,
|
|
520
551
|
branch=dependency.branch,
|
|
552
|
+
stderr=e.stderr,
|
|
521
553
|
) from e
|
|
522
554
|
|
|
523
555
|
# Setup config for partial clone and sparse checkout
|
|
@@ -535,10 +567,18 @@ def _checkout_dependency(
|
|
|
535
567
|
branch=dependency.branch,
|
|
536
568
|
) from e
|
|
537
569
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
570
|
+
try:
|
|
571
|
+
_run_command(
|
|
572
|
+
["git", "checkout", dependency.branch],
|
|
573
|
+
cwd=temp_dir,
|
|
574
|
+
)
|
|
575
|
+
except subprocess.CalledProcessError as e:
|
|
576
|
+
raise DependencyError(
|
|
577
|
+
repo_name=dependency.repo_name,
|
|
578
|
+
repo_link=dependency.repo_link,
|
|
579
|
+
branch=dependency.branch,
|
|
580
|
+
stderr=e.stderr,
|
|
581
|
+
) from e
|
|
542
582
|
|
|
543
583
|
# Clean up the existing directory if it exists
|
|
544
584
|
if os.path.exists(dependency_repo_dir):
|
|
@@ -580,12 +620,26 @@ def _has_remote_config(remote_config: RemoteConfig | None) -> TypeGuard[RemoteCo
|
|
|
580
620
|
return remote_config is not None
|
|
581
621
|
|
|
582
622
|
|
|
623
|
+
def _rev_parse(repo_dir: str, ref: str) -> str:
|
|
624
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
625
|
+
logger.debug("Parsing revision for %s (%s)", ref, repo_dir)
|
|
626
|
+
rev = (
|
|
627
|
+
subprocess.check_output(
|
|
628
|
+
["git", "rev-parse", ref], cwd=repo_dir, stderr=subprocess.PIPE
|
|
629
|
+
)
|
|
630
|
+
.strip()
|
|
631
|
+
.decode()
|
|
632
|
+
)
|
|
633
|
+
logger.debug("Parsed revision %s for %s (%s)", rev, ref, repo_dir)
|
|
634
|
+
return rev
|
|
635
|
+
|
|
636
|
+
|
|
583
637
|
def _run_command(
|
|
584
638
|
cmd: list[str], cwd: str, stdout: int | TextIO | None = subprocess.DEVNULL
|
|
585
639
|
) -> None:
|
|
586
640
|
logger = logging.getLogger(LOGGER_NAME)
|
|
587
|
-
logger.debug(
|
|
588
|
-
subprocess.run(cmd, cwd=cwd, check=True, stdout=stdout, stderr=subprocess.
|
|
641
|
+
logger.debug("Running command: %s in %s", " ".join(cmd), cwd)
|
|
642
|
+
subprocess.run(cmd, cwd=cwd, check=True, stdout=stdout, stderr=subprocess.PIPE)
|
|
589
643
|
|
|
590
644
|
|
|
591
645
|
def _run_command_with_retries(
|
|
@@ -601,7 +655,13 @@ def _run_command_with_retries(
|
|
|
601
655
|
break
|
|
602
656
|
except subprocess.CalledProcessError as e:
|
|
603
657
|
logger = logging.getLogger(LOGGER_NAME)
|
|
604
|
-
logger.
|
|
658
|
+
logger.debug(
|
|
659
|
+
"Attempt %s of %s for %s failed: %s", i + 1, retries, cmd, e.stderr
|
|
660
|
+
)
|
|
661
|
+
capture_message(
|
|
662
|
+
f"Attempt {i + 1} of {retries} for {cmd} failed: {e.stderr}",
|
|
663
|
+
level="warning",
|
|
664
|
+
)
|
|
605
665
|
if i == retries - 1:
|
|
606
666
|
raise e
|
|
607
667
|
time.sleep(backoff**i)
|
|
@@ -270,7 +270,7 @@ def get_docker_compose_commands_to_run(
|
|
|
270
270
|
def run_cmd(cmd: list[str], env: dict[str, str]) -> subprocess.CompletedProcess[str]:
|
|
271
271
|
logger = logging.getLogger(LOGGER_NAME)
|
|
272
272
|
try:
|
|
273
|
-
logger.debug(
|
|
273
|
+
logger.debug("Running command: %s", " ".join(cmd))
|
|
274
274
|
return subprocess.run(cmd, check=True, capture_output=True, text=True, env=env)
|
|
275
275
|
except subprocess.CalledProcessError as e:
|
|
276
276
|
raise DockerComposeError(
|
|
@@ -2,11 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sqlite3
|
|
5
|
+
from enum import Enum
|
|
5
6
|
|
|
6
7
|
from devservices.constants import DEVSERVICES_LOCAL_DIR
|
|
7
8
|
from devservices.constants import STATE_DB_FILE
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
class StateTables(Enum):
|
|
12
|
+
STARTED_SERVICES = "started_services"
|
|
13
|
+
STARTING_SERVICES = "starting_services"
|
|
14
|
+
|
|
15
|
+
|
|
10
16
|
class State:
|
|
11
17
|
_instance: State | None = None
|
|
12
18
|
state_db_file: str
|
|
@@ -24,9 +30,20 @@ class State:
|
|
|
24
30
|
|
|
25
31
|
def initialize_database(self) -> None:
|
|
26
32
|
cursor = self.conn.cursor()
|
|
33
|
+
# Formatted strings here and throughout the fileshould be extremely low risk given these are constants
|
|
27
34
|
cursor.execute(
|
|
28
|
-
"""
|
|
29
|
-
CREATE TABLE IF NOT EXISTS
|
|
35
|
+
f"""
|
|
36
|
+
CREATE TABLE IF NOT EXISTS {StateTables.STARTED_SERVICES.value} (
|
|
37
|
+
service_name TEXT PRIMARY KEY,
|
|
38
|
+
mode TEXT,
|
|
39
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
40
|
+
)
|
|
41
|
+
"""
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
cursor.execute(
|
|
45
|
+
f"""
|
|
46
|
+
CREATE TABLE IF NOT EXISTS {StateTables.STARTING_SERVICES.value} (
|
|
30
47
|
service_name TEXT PRIMARY KEY,
|
|
31
48
|
mode TEXT,
|
|
32
49
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
@@ -35,52 +52,56 @@ class State:
|
|
|
35
52
|
)
|
|
36
53
|
self.conn.commit()
|
|
37
54
|
|
|
38
|
-
def
|
|
55
|
+
def update_service_entry(
|
|
56
|
+
self, service_name: str, mode: str, table: StateTables
|
|
57
|
+
) -> None:
|
|
39
58
|
cursor = self.conn.cursor()
|
|
40
|
-
|
|
41
|
-
active_modes = self.get_active_modes_for_service(service_name)
|
|
42
|
-
if service_name in
|
|
59
|
+
service_entries = self.get_service_entries(table)
|
|
60
|
+
active_modes = self.get_active_modes_for_service(service_name, table)
|
|
61
|
+
if service_name in service_entries and mode in active_modes:
|
|
43
62
|
return
|
|
44
|
-
if service_name in
|
|
63
|
+
if service_name in service_entries:
|
|
45
64
|
cursor.execute(
|
|
46
|
-
"""
|
|
47
|
-
UPDATE
|
|
65
|
+
f"""
|
|
66
|
+
UPDATE {table.value} SET mode = ? WHERE service_name = ?
|
|
48
67
|
""",
|
|
49
68
|
(",".join(active_modes + [mode]), service_name),
|
|
50
69
|
)
|
|
51
70
|
else:
|
|
52
71
|
cursor.execute(
|
|
53
|
-
"""
|
|
54
|
-
INSERT INTO
|
|
72
|
+
f"""
|
|
73
|
+
INSERT INTO {table.value} (service_name, mode) VALUES (?, ?)
|
|
55
74
|
""",
|
|
56
75
|
(service_name, ",".join(active_modes + [mode])),
|
|
57
76
|
)
|
|
58
77
|
self.conn.commit()
|
|
59
78
|
|
|
60
|
-
def
|
|
79
|
+
def remove_service_entry(self, service_name: str, table: StateTables) -> None:
|
|
61
80
|
cursor = self.conn.cursor()
|
|
62
81
|
cursor.execute(
|
|
63
|
-
"""
|
|
64
|
-
DELETE FROM
|
|
82
|
+
f"""
|
|
83
|
+
DELETE FROM {table.value} WHERE service_name = ?
|
|
65
84
|
""",
|
|
66
85
|
(service_name,),
|
|
67
86
|
)
|
|
68
87
|
self.conn.commit()
|
|
69
88
|
|
|
70
|
-
def
|
|
89
|
+
def get_service_entries(self, table: StateTables) -> list[str]:
|
|
71
90
|
cursor = self.conn.cursor()
|
|
72
91
|
cursor.execute(
|
|
73
|
-
"""
|
|
74
|
-
SELECT service_name FROM
|
|
92
|
+
f"""
|
|
93
|
+
SELECT service_name FROM {table.value}
|
|
75
94
|
"""
|
|
76
95
|
)
|
|
77
96
|
return [row[0] for row in cursor.fetchall()]
|
|
78
97
|
|
|
79
|
-
def get_active_modes_for_service(
|
|
98
|
+
def get_active_modes_for_service(
|
|
99
|
+
self, service_name: str, table: StateTables
|
|
100
|
+
) -> list[str]:
|
|
80
101
|
cursor = self.conn.cursor()
|
|
81
102
|
cursor.execute(
|
|
82
|
-
"""
|
|
83
|
-
SELECT mode FROM
|
|
103
|
+
f"""
|
|
104
|
+
SELECT mode FROM {table.value} WHERE service_name = ?
|
|
84
105
|
""",
|
|
85
106
|
(service_name,),
|
|
86
107
|
)
|
|
@@ -92,8 +113,13 @@ class State:
|
|
|
92
113
|
def clear_state(self) -> None:
|
|
93
114
|
cursor = self.conn.cursor()
|
|
94
115
|
cursor.execute(
|
|
95
|
-
"""
|
|
96
|
-
DELETE FROM
|
|
116
|
+
f"""
|
|
117
|
+
DELETE FROM {StateTables.STARTED_SERVICES.value}
|
|
118
|
+
"""
|
|
119
|
+
)
|
|
120
|
+
cursor.execute(
|
|
121
|
+
f"""
|
|
122
|
+
DELETE FROM {StateTables.STARTING_SERVICES.value}
|
|
97
123
|
"""
|
|
98
124
|
)
|
|
99
125
|
self.conn.commit()
|