devservices 1.0.4__tar.gz → 1.0.5__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.4 → devservices-1.0.5}/PKG-INFO +1 -1
- {devservices-1.0.4 → devservices-1.0.5}/README.md +1 -1
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/down.py +13 -5
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/list_dependencies.py +5 -2
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/list_services.py +2 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/logs.py +4 -1
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/status.py +4 -1
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/up.py +9 -57
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/dependencies.py +37 -10
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/devenv.py +1 -1
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/state.py +20 -11
- {devservices-1.0.4 → devservices-1.0.5}/devservices.egg-info/PKG-INFO +1 -1
- {devservices-1.0.4 → devservices-1.0.5}/devservices.egg-info/SOURCES.txt +3 -0
- {devservices-1.0.4 → devservices-1.0.5}/pyproject.toml +1 -1
- {devservices-1.0.4 → devservices-1.0.5}/tests/commands/test_down.py +35 -3
- devservices-1.0.5/tests/commands/test_list_dependencies.py +111 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/commands/test_list_services.py +4 -4
- {devservices-1.0.4 → devservices-1.0.5}/tests/commands/test_logs.py +33 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/commands/test_purge.py +3 -3
- devservices-1.0.5/tests/commands/test_status.py +171 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/commands/test_up.py +53 -58
- {devservices-1.0.4 → devservices-1.0.5}/tests/utils/test_dependencies.py +162 -15
- devservices-1.0.5/tests/utils/test_services.py +102 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/utils/test_state.py +10 -10
- {devservices-1.0.4 → devservices-1.0.5}/LICENSE.md +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/__init__.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/__init__.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/check_for_update.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/purge.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/commands/update.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/configs/service_config.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/constants.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/exceptions.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/main.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/__init__.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/console.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/docker.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/docker_compose.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/file_lock.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/install_binary.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices/utils/services.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices.egg-info/requires.txt +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/setup.cfg +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/testing/__init__.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/testing/utils.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/__init__.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/commands/test_update.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/configs/test_service_config.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/conftest.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/utils/test_docker.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/utils/test_docker_compose.py +0 -0
- {devservices-1.0.4 → devservices-1.0.5}/tests/utils/test_install_binary.py +0 -0
|
@@ -56,10 +56,13 @@ def down(args: Namespace) -> None:
|
|
|
56
56
|
service_name = args.service_name
|
|
57
57
|
try:
|
|
58
58
|
service = find_matching_service(service_name)
|
|
59
|
-
except
|
|
59
|
+
except ConfigError as e:
|
|
60
60
|
capture_exception(e)
|
|
61
61
|
console.failure(str(e))
|
|
62
62
|
exit(1)
|
|
63
|
+
except ServiceNotFoundError as e:
|
|
64
|
+
console.failure(str(e))
|
|
65
|
+
exit(1)
|
|
63
66
|
|
|
64
67
|
modes = service.config.modes
|
|
65
68
|
|
|
@@ -69,15 +72,20 @@ def down(args: Namespace) -> None:
|
|
|
69
72
|
console.warning(f"{service.name} is not running")
|
|
70
73
|
exit(0)
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
mode_dependencies =
|
|
75
|
+
active_modes = state.get_active_modes_for_service(service.name)
|
|
76
|
+
mode_dependencies = set()
|
|
77
|
+
for active_mode in active_modes:
|
|
78
|
+
active_mode_dependencies = modes.get(active_mode, [])
|
|
79
|
+
mode_dependencies.update(active_mode_dependencies)
|
|
74
80
|
|
|
75
81
|
with Status(
|
|
76
82
|
lambda: console.warning(f"Stopping {service.name}"),
|
|
77
83
|
lambda: console.success(f"{service.name} stopped"),
|
|
78
84
|
) as status:
|
|
79
85
|
try:
|
|
80
|
-
remote_dependencies = install_and_verify_dependencies(
|
|
86
|
+
remote_dependencies = install_and_verify_dependencies(
|
|
87
|
+
service, modes=active_modes
|
|
88
|
+
)
|
|
81
89
|
except DependencyError as de:
|
|
82
90
|
capture_exception(de)
|
|
83
91
|
status.failure(str(de))
|
|
@@ -86,7 +94,7 @@ def down(args: Namespace) -> None:
|
|
|
86
94
|
service, remote_dependencies
|
|
87
95
|
)
|
|
88
96
|
try:
|
|
89
|
-
_down(service, remote_dependencies, mode_dependencies, status)
|
|
97
|
+
_down(service, remote_dependencies, list(mode_dependencies), status)
|
|
90
98
|
except DockerComposeError as dce:
|
|
91
99
|
capture_exception(dce)
|
|
92
100
|
status.failure(f"Failed to stop {service.name}: {dce.stderr}")
|
|
@@ -32,10 +32,13 @@ def list_dependencies(args: Namespace) -> None:
|
|
|
32
32
|
|
|
33
33
|
try:
|
|
34
34
|
service = find_matching_service(service_name)
|
|
35
|
-
except
|
|
35
|
+
except ConfigError as e:
|
|
36
36
|
capture_exception(e)
|
|
37
37
|
console.failure(str(e))
|
|
38
38
|
exit(1)
|
|
39
|
+
except ServiceNotFoundError as e:
|
|
40
|
+
console.failure(str(e))
|
|
41
|
+
exit(1)
|
|
39
42
|
|
|
40
43
|
dependencies = service.config.dependencies
|
|
41
44
|
|
|
@@ -45,4 +48,4 @@ def list_dependencies(args: Namespace) -> None:
|
|
|
45
48
|
|
|
46
49
|
console.info(f"Dependencies of {service.name}:")
|
|
47
50
|
for dependency_key, dependency_info in dependencies.items():
|
|
48
|
-
console.info("-" + dependency_key + ":" + dependency_info.description)
|
|
51
|
+
console.info("- " + dependency_key + ": " + dependency_info.description)
|
|
@@ -47,7 +47,9 @@ def list_services(args: Namespace) -> None:
|
|
|
47
47
|
|
|
48
48
|
for service in services_to_show:
|
|
49
49
|
status = "running" if service.name in running_services else "stopped"
|
|
50
|
+
active_modes = state.get_active_modes_for_service(service.name)
|
|
50
51
|
console.info(f"- {service.name}")
|
|
52
|
+
console.info(f" modes: {active_modes}")
|
|
51
53
|
console.info(f" status: {status}")
|
|
52
54
|
console.info(f" location: {service.repo_path}")
|
|
53
55
|
|
|
@@ -46,10 +46,13 @@ def logs(args: Namespace) -> None:
|
|
|
46
46
|
service_name = args.service_name
|
|
47
47
|
try:
|
|
48
48
|
service = find_matching_service(service_name)
|
|
49
|
-
except
|
|
49
|
+
except ConfigError as e:
|
|
50
50
|
capture_exception(e)
|
|
51
51
|
console.failure(str(e))
|
|
52
52
|
exit(1)
|
|
53
|
+
except ServiceNotFoundError as e:
|
|
54
|
+
console.failure(str(e))
|
|
55
|
+
exit(1)
|
|
53
56
|
|
|
54
57
|
modes = service.config.modes
|
|
55
58
|
# TODO: allow custom modes to be used
|
|
@@ -81,10 +81,13 @@ def status(args: Namespace) -> None:
|
|
|
81
81
|
service_name = args.service_name
|
|
82
82
|
try:
|
|
83
83
|
service = find_matching_service(service_name)
|
|
84
|
-
except
|
|
84
|
+
except ConfigError as e:
|
|
85
85
|
capture_exception(e)
|
|
86
86
|
console.failure(str(e))
|
|
87
87
|
exit(1)
|
|
88
|
+
except ServiceNotFoundError as e:
|
|
89
|
+
console.failure(str(e))
|
|
90
|
+
exit(1)
|
|
88
91
|
|
|
89
92
|
modes = service.config.modes
|
|
90
93
|
# TODO: allow custom modes to be used
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import concurrent.futures
|
|
4
3
|
import os
|
|
5
4
|
import subprocess
|
|
6
5
|
from argparse import _SubParsersAction
|
|
@@ -23,7 +22,6 @@ from devservices.exceptions import ServiceNotFoundError
|
|
|
23
22
|
from devservices.utils.console import Console
|
|
24
23
|
from devservices.utils.console import Status
|
|
25
24
|
from devservices.utils.dependencies import construct_dependency_graph
|
|
26
|
-
from devservices.utils.dependencies import get_non_shared_remote_dependencies
|
|
27
25
|
from devservices.utils.dependencies import install_and_verify_dependencies
|
|
28
26
|
from devservices.utils.dependencies import InstalledRemoteDependency
|
|
29
27
|
from devservices.utils.docker_compose import get_docker_compose_commands_to_run
|
|
@@ -58,64 +56,17 @@ def up(args: Namespace) -> None:
|
|
|
58
56
|
service_name = args.service_name
|
|
59
57
|
try:
|
|
60
58
|
service = find_matching_service(service_name)
|
|
61
|
-
except
|
|
59
|
+
except ConfigError as e:
|
|
62
60
|
capture_exception(e)
|
|
63
61
|
console.failure(str(e))
|
|
64
62
|
exit(1)
|
|
63
|
+
except ServiceNotFoundError as e:
|
|
64
|
+
console.failure(str(e))
|
|
65
|
+
exit(1)
|
|
65
66
|
|
|
66
67
|
modes = service.config.modes
|
|
67
68
|
mode = args.mode
|
|
68
69
|
|
|
69
|
-
state = State()
|
|
70
|
-
started_services = state.get_started_services()
|
|
71
|
-
running_mode = state.get_mode_for_service(service.name) or "default"
|
|
72
|
-
|
|
73
|
-
# TODO: Remove this once we properly handle mode switching
|
|
74
|
-
if service.name in started_services and running_mode != mode:
|
|
75
|
-
console.warning(
|
|
76
|
-
f"Service '{service.name}' is already running in mode: '{running_mode}', restarting in mode: '{mode}'"
|
|
77
|
-
)
|
|
78
|
-
with Status() as status:
|
|
79
|
-
try:
|
|
80
|
-
remote_dependencies = install_and_verify_dependencies(
|
|
81
|
-
service, mode=running_mode
|
|
82
|
-
)
|
|
83
|
-
except DependencyError as de:
|
|
84
|
-
capture_exception(de)
|
|
85
|
-
status.failure(str(de))
|
|
86
|
-
exit(1)
|
|
87
|
-
except ModeDoesNotExistError as mde:
|
|
88
|
-
capture_exception(mde)
|
|
89
|
-
status.failure(str(mde))
|
|
90
|
-
exit(1)
|
|
91
|
-
service_config_file_path = os.path.join(
|
|
92
|
-
service.repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME
|
|
93
|
-
)
|
|
94
|
-
current_env = os.environ.copy()
|
|
95
|
-
running_mode_dependencies = modes[running_mode]
|
|
96
|
-
remote_dependencies_to_bring_down = get_non_shared_remote_dependencies(
|
|
97
|
-
service, remote_dependencies
|
|
98
|
-
)
|
|
99
|
-
down_docker_compose_commands = get_docker_compose_commands_to_run(
|
|
100
|
-
service=service,
|
|
101
|
-
remote_dependencies=list(remote_dependencies_to_bring_down),
|
|
102
|
-
current_env=current_env,
|
|
103
|
-
command="down",
|
|
104
|
-
options=[],
|
|
105
|
-
service_config_file_path=service_config_file_path,
|
|
106
|
-
mode_dependencies=running_mode_dependencies,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
110
|
-
futures = [
|
|
111
|
-
executor.submit(run_cmd, cmd, current_env)
|
|
112
|
-
for cmd in down_docker_compose_commands
|
|
113
|
-
]
|
|
114
|
-
for future in concurrent.futures.as_completed(futures):
|
|
115
|
-
future.result()
|
|
116
|
-
|
|
117
|
-
state.remove_started_service(service.name)
|
|
118
|
-
|
|
119
70
|
with Status(
|
|
120
71
|
lambda: console.warning(f"Starting '{service.name}' in mode: '{mode}'"),
|
|
121
72
|
lambda: console.success(f"{service.name} started"),
|
|
@@ -123,7 +74,7 @@ def up(args: Namespace) -> None:
|
|
|
123
74
|
try:
|
|
124
75
|
status.info("Retrieving dependencies")
|
|
125
76
|
remote_dependencies = install_and_verify_dependencies(
|
|
126
|
-
service, force_update_dependencies=True,
|
|
77
|
+
service, force_update_dependencies=True, modes=[mode]
|
|
127
78
|
)
|
|
128
79
|
except DependencyError as de:
|
|
129
80
|
capture_exception(de)
|
|
@@ -139,14 +90,14 @@ def up(args: Namespace) -> None:
|
|
|
139
90
|
pass
|
|
140
91
|
try:
|
|
141
92
|
mode_dependencies = modes[mode]
|
|
142
|
-
_up(service, remote_dependencies, mode_dependencies, status)
|
|
93
|
+
_up(service, [mode], remote_dependencies, mode_dependencies, status)
|
|
143
94
|
except DockerComposeError as dce:
|
|
144
95
|
capture_exception(dce)
|
|
145
96
|
status.failure(f"Failed to start {service.name}: {dce.stderr}")
|
|
146
97
|
exit(1)
|
|
147
98
|
# TODO: We should factor in healthchecks here before marking service as running
|
|
148
99
|
state = State()
|
|
149
|
-
state.
|
|
100
|
+
state.update_started_service(service.name, mode)
|
|
150
101
|
|
|
151
102
|
|
|
152
103
|
def _bring_up_dependency(
|
|
@@ -160,6 +111,7 @@ def _bring_up_dependency(
|
|
|
160
111
|
|
|
161
112
|
def _up(
|
|
162
113
|
service: Service,
|
|
114
|
+
modes: list[str],
|
|
163
115
|
remote_dependencies: set[InstalledRemoteDependency],
|
|
164
116
|
mode_dependencies: list[str],
|
|
165
117
|
status: Status,
|
|
@@ -177,7 +129,7 @@ def _up(
|
|
|
177
129
|
DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
178
130
|
] = relative_local_dependency_directory
|
|
179
131
|
options = ["-d"]
|
|
180
|
-
dependency_graph = construct_dependency_graph(service)
|
|
132
|
+
dependency_graph = construct_dependency_graph(service, modes=modes)
|
|
181
133
|
starting_order = dependency_graph.get_starting_order()
|
|
182
134
|
sorted_remote_dependencies = sorted(
|
|
183
135
|
remote_dependencies, key=lambda dep: starting_order.index(dep.service_name)
|
|
@@ -160,11 +160,20 @@ class GitConfigManager:
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
def install_and_verify_dependencies(
|
|
163
|
-
service: Service,
|
|
163
|
+
service: Service,
|
|
164
|
+
force_update_dependencies: bool = False,
|
|
165
|
+
modes: list[str] | None = None,
|
|
164
166
|
) -> set[InstalledRemoteDependency]:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
"""
|
|
168
|
+
Install and verify dependencies for a service
|
|
169
|
+
"""
|
|
170
|
+
if modes is None:
|
|
171
|
+
modes = ["default"]
|
|
172
|
+
mode_dependencies = set()
|
|
173
|
+
for mode in modes:
|
|
174
|
+
if mode not in service.config.modes:
|
|
175
|
+
raise ModeDoesNotExistError(service_name=service.name, mode=mode)
|
|
176
|
+
mode_dependencies.update(service.config.modes[mode])
|
|
168
177
|
matching_dependencies = [
|
|
169
178
|
dependency
|
|
170
179
|
for dependency_key, dependency in list(service.config.dependencies.items())
|
|
@@ -346,8 +355,18 @@ def install_dependency(dependency: RemoteConfig) -> set[InstalledRemoteDependenc
|
|
|
346
355
|
branch=dependency.branch,
|
|
347
356
|
) from e
|
|
348
357
|
|
|
349
|
-
|
|
350
|
-
|
|
358
|
+
if dependency.mode not in installed_config.modes:
|
|
359
|
+
raise ModeDoesNotExistError(
|
|
360
|
+
service_name=installed_config.service_name,
|
|
361
|
+
mode=dependency.mode,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
active_nested_dependencies = [
|
|
365
|
+
nested_dependency
|
|
366
|
+
for nested_dependency_name, nested_dependency in installed_config.dependencies.items()
|
|
367
|
+
if nested_dependency_name in installed_config.modes[dependency.mode]
|
|
368
|
+
]
|
|
369
|
+
nested_remote_configs = _get_remote_configs(active_nested_dependencies)
|
|
351
370
|
|
|
352
371
|
installed_dependencies: set[InstalledRemoteDependency] = set(
|
|
353
372
|
[
|
|
@@ -526,15 +545,23 @@ def get_remote_dependency_config(remote_config: RemoteConfig) -> ServiceConfig:
|
|
|
526
545
|
return load_service_config_from_file(dependency_repo_dir)
|
|
527
546
|
|
|
528
547
|
|
|
529
|
-
def construct_dependency_graph(service: Service) -> DependencyGraph:
|
|
548
|
+
def construct_dependency_graph(service: Service, modes: list[str]) -> DependencyGraph:
|
|
530
549
|
dependency_graph = DependencyGraph()
|
|
531
550
|
|
|
532
|
-
def _construct_dependency_graph(
|
|
551
|
+
def _construct_dependency_graph(
|
|
552
|
+
service_config: ServiceConfig, modes: list[str]
|
|
553
|
+
) -> None:
|
|
554
|
+
service_mode_dependencies = set()
|
|
555
|
+
for mode in modes:
|
|
556
|
+
service_mode_dependencies.update(service_config.modes.get(mode, []))
|
|
533
557
|
for dependency_name, dependency in service_config.dependencies.items():
|
|
558
|
+
# Skip the dependency if it's not in the modes (since it may not be installed and we don't care about it)
|
|
559
|
+
if dependency_name not in service_mode_dependencies:
|
|
560
|
+
continue
|
|
534
561
|
dependency_graph.add_edge(service_config.service_name, dependency_name)
|
|
535
562
|
if _has_remote_config(dependency.remote):
|
|
536
563
|
dependency_config = get_remote_dependency_config(dependency.remote)
|
|
537
|
-
_construct_dependency_graph(dependency_config)
|
|
564
|
+
_construct_dependency_graph(dependency_config, [dependency.remote.mode])
|
|
538
565
|
|
|
539
|
-
_construct_dependency_graph(service.config)
|
|
566
|
+
_construct_dependency_graph(service.config, modes)
|
|
540
567
|
return dependency_graph
|
|
@@ -13,7 +13,7 @@ def get_coderoot() -> str:
|
|
|
13
13
|
config_path = os.path.join(home, ".config", "sentry-devenv", "config.ini")
|
|
14
14
|
try:
|
|
15
15
|
devenv_config: ConfigParser = read_config(config_path)
|
|
16
|
-
return devenv_config.get("devenv", "coderoot", fallback="")
|
|
16
|
+
return os.path.expanduser(devenv_config.get("devenv", "coderoot", fallback=""))
|
|
17
17
|
except (FileNotFoundError, NoSectionError, NoOptionError):
|
|
18
18
|
# TODO: Handle the case where there is no config file or the coderoot is not set
|
|
19
19
|
raise Exception("Failed to read code root from config")
|
|
@@ -35,17 +35,26 @@ class State:
|
|
|
35
35
|
)
|
|
36
36
|
self.conn.commit()
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def update_started_service(self, service_name: str, mode: str) -> None:
|
|
39
39
|
cursor = self.conn.cursor()
|
|
40
40
|
started_services = self.get_started_services()
|
|
41
|
-
|
|
41
|
+
active_modes = self.get_active_modes_for_service(service_name)
|
|
42
|
+
if service_name in started_services and mode in active_modes:
|
|
42
43
|
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
if service_name in started_services:
|
|
45
|
+
cursor.execute(
|
|
46
|
+
"""
|
|
47
|
+
UPDATE started_services SET mode = ? WHERE service_name = ?
|
|
48
|
+
""",
|
|
49
|
+
(",".join(active_modes + [mode]), service_name),
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
cursor.execute(
|
|
53
|
+
"""
|
|
54
|
+
INSERT INTO started_services (service_name, mode) VALUES (?, ?)
|
|
55
|
+
""",
|
|
56
|
+
(service_name, ",".join(active_modes + [mode])),
|
|
57
|
+
)
|
|
49
58
|
self.conn.commit()
|
|
50
59
|
|
|
51
60
|
def remove_started_service(self, service_name: str) -> None:
|
|
@@ -67,7 +76,7 @@ class State:
|
|
|
67
76
|
)
|
|
68
77
|
return [row[0] for row in cursor.fetchall()]
|
|
69
78
|
|
|
70
|
-
def
|
|
79
|
+
def get_active_modes_for_service(self, service_name: str) -> list[str]:
|
|
71
80
|
cursor = self.conn.cursor()
|
|
72
81
|
cursor.execute(
|
|
73
82
|
"""
|
|
@@ -77,8 +86,8 @@ class State:
|
|
|
77
86
|
)
|
|
78
87
|
result = cursor.fetchone()
|
|
79
88
|
if result is None:
|
|
80
|
-
return
|
|
81
|
-
return str(result[0])
|
|
89
|
+
return []
|
|
90
|
+
return str(result[0]).split(",")
|
|
82
91
|
|
|
83
92
|
def clear_state(self) -> None:
|
|
84
93
|
cursor = self.conn.cursor()
|
|
@@ -38,9 +38,11 @@ testing/utils.py
|
|
|
38
38
|
tests/__init__.py
|
|
39
39
|
tests/conftest.py
|
|
40
40
|
tests/commands/test_down.py
|
|
41
|
+
tests/commands/test_list_dependencies.py
|
|
41
42
|
tests/commands/test_list_services.py
|
|
42
43
|
tests/commands/test_logs.py
|
|
43
44
|
tests/commands/test_purge.py
|
|
45
|
+
tests/commands/test_status.py
|
|
44
46
|
tests/commands/test_up.py
|
|
45
47
|
tests/commands/test_update.py
|
|
46
48
|
tests/configs/test_service_config.py
|
|
@@ -48,4 +50,5 @@ tests/utils/test_dependencies.py
|
|
|
48
50
|
tests/utils/test_docker.py
|
|
49
51
|
tests/utils/test_docker_compose.py
|
|
50
52
|
tests/utils/test_install_binary.py
|
|
53
|
+
tests/utils/test_services.py
|
|
51
54
|
tests/utils/test_state.py
|
|
@@ -13,6 +13,8 @@ from devservices.constants import CONFIG_FILE_NAME
|
|
|
13
13
|
from devservices.constants import DEPENDENCY_CONFIG_VERSION
|
|
14
14
|
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
15
15
|
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
16
|
+
from devservices.exceptions import ConfigError
|
|
17
|
+
from devservices.exceptions import ServiceNotFoundError
|
|
16
18
|
from devservices.utils.state import State
|
|
17
19
|
from testing.utils import create_config_file
|
|
18
20
|
|
|
@@ -64,7 +66,7 @@ def test_down_simple(
|
|
|
64
66
|
"devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")
|
|
65
67
|
):
|
|
66
68
|
state = State()
|
|
67
|
-
state.
|
|
69
|
+
state.update_started_service("example-service", "default")
|
|
68
70
|
down(args)
|
|
69
71
|
|
|
70
72
|
# Ensure the DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY is set and is relative
|
|
@@ -135,7 +137,7 @@ def test_down_error(
|
|
|
135
137
|
|
|
136
138
|
with mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
|
|
137
139
|
state = State()
|
|
138
|
-
state.
|
|
140
|
+
state.update_started_service("example-service", "default")
|
|
139
141
|
with pytest.raises(SystemExit):
|
|
140
142
|
down(args)
|
|
141
143
|
|
|
@@ -200,7 +202,7 @@ def test_down_mode_simple(
|
|
|
200
202
|
"devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")
|
|
201
203
|
):
|
|
202
204
|
state = State()
|
|
203
|
-
state.
|
|
205
|
+
state.update_started_service("example-service", "test")
|
|
204
206
|
down(args)
|
|
205
207
|
|
|
206
208
|
# Ensure the DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY is set and is relative
|
|
@@ -231,3 +233,33 @@ def test_down_mode_simple(
|
|
|
231
233
|
|
|
232
234
|
captured = capsys.readouterr()
|
|
233
235
|
assert "Stopping redis" in captured.out.strip()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@mock.patch("devservices.commands.down.find_matching_service")
|
|
239
|
+
def test_down_config_error(
|
|
240
|
+
find_matching_service_mock: mock.Mock, capsys: pytest.CaptureFixture[str]
|
|
241
|
+
) -> None:
|
|
242
|
+
find_matching_service_mock.side_effect = ConfigError("Config error")
|
|
243
|
+
args = Namespace(service_name="example-service", debug=False)
|
|
244
|
+
|
|
245
|
+
with pytest.raises(SystemExit):
|
|
246
|
+
down(args)
|
|
247
|
+
|
|
248
|
+
find_matching_service_mock.assert_called_once_with("example-service")
|
|
249
|
+
captured = capsys.readouterr()
|
|
250
|
+
assert "Config error" in captured.out.strip()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@mock.patch("devservices.commands.down.find_matching_service")
|
|
254
|
+
def test_down_service_not_found_error(
|
|
255
|
+
find_matching_service_mock: mock.Mock, capsys: pytest.CaptureFixture[str]
|
|
256
|
+
) -> None:
|
|
257
|
+
find_matching_service_mock.side_effect = ServiceNotFoundError("Service not found")
|
|
258
|
+
args = Namespace(service_name="example-service", debug=False)
|
|
259
|
+
|
|
260
|
+
with pytest.raises(SystemExit):
|
|
261
|
+
down(args)
|
|
262
|
+
|
|
263
|
+
find_matching_service_mock.assert_called_once_with("example-service")
|
|
264
|
+
captured = capsys.readouterr()
|
|
265
|
+
assert "Service not found" in captured.out.strip()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from argparse import Namespace
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest import mock
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from devservices.commands.list_dependencies import list_dependencies
|
|
10
|
+
from devservices.configs.service_config import Dependency
|
|
11
|
+
from devservices.configs.service_config import ServiceConfig
|
|
12
|
+
from devservices.exceptions import ConfigValidationError
|
|
13
|
+
from devservices.exceptions import ServiceNotFoundError
|
|
14
|
+
from devservices.utils.services import Service
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mock.patch("devservices.commands.list_dependencies.find_matching_service")
|
|
18
|
+
def test_list_dependencies_service_not_found(
|
|
19
|
+
mock_find_matching_service: mock.Mock,
|
|
20
|
+
capsys: pytest.CaptureFixture[str],
|
|
21
|
+
) -> None:
|
|
22
|
+
args = Namespace(service_name="nonexistent-service")
|
|
23
|
+
mock_find_matching_service.side_effect = ServiceNotFoundError(
|
|
24
|
+
"Service nonexistent-service not found"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
28
|
+
list_dependencies(args)
|
|
29
|
+
|
|
30
|
+
assert exc_info.value.code == 1
|
|
31
|
+
|
|
32
|
+
mock_find_matching_service.assert_called_once_with("nonexistent-service")
|
|
33
|
+
captured = capsys.readouterr()
|
|
34
|
+
assert "Service nonexistent-service not found" in captured.out
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@mock.patch("devservices.commands.list_dependencies.find_matching_service")
|
|
38
|
+
def test_list_dependencies_config_error(
|
|
39
|
+
mock_find_matching_service: mock.Mock,
|
|
40
|
+
capsys: pytest.CaptureFixture[str],
|
|
41
|
+
) -> None:
|
|
42
|
+
args = Namespace(service_name="test-service")
|
|
43
|
+
mock_find_matching_service.side_effect = ConfigValidationError(
|
|
44
|
+
"Version is required in service config"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
48
|
+
list_dependencies(args)
|
|
49
|
+
|
|
50
|
+
assert exc_info.value.code == 1
|
|
51
|
+
|
|
52
|
+
mock_find_matching_service.assert_called_once_with("test-service")
|
|
53
|
+
captured = capsys.readouterr()
|
|
54
|
+
assert "Version is required in service config" in captured.out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@mock.patch("devservices.commands.list_dependencies.find_matching_service")
|
|
58
|
+
def test_list_dependencies_no_dependencies(
|
|
59
|
+
mock_find_matching_service: mock.Mock,
|
|
60
|
+
capsys: pytest.CaptureFixture[str],
|
|
61
|
+
tmp_path: Path,
|
|
62
|
+
) -> None:
|
|
63
|
+
args = Namespace(service_name="test-service")
|
|
64
|
+
service = Service(
|
|
65
|
+
name="test-service",
|
|
66
|
+
repo_path=str(tmp_path),
|
|
67
|
+
config=ServiceConfig(
|
|
68
|
+
version=0.1,
|
|
69
|
+
service_name="test-service",
|
|
70
|
+
dependencies={},
|
|
71
|
+
modes={"default": []},
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
mock_find_matching_service.return_value = service
|
|
75
|
+
|
|
76
|
+
list_dependencies(args)
|
|
77
|
+
|
|
78
|
+
mock_find_matching_service.assert_called_once_with("test-service")
|
|
79
|
+
captured = capsys.readouterr()
|
|
80
|
+
assert "No dependencies found for test-service" in captured.out
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@mock.patch("devservices.commands.list_dependencies.find_matching_service")
|
|
84
|
+
def test_list_dependencies_with_dependencies(
|
|
85
|
+
mock_find_matching_service: mock.Mock,
|
|
86
|
+
capsys: pytest.CaptureFixture[str],
|
|
87
|
+
tmp_path: Path,
|
|
88
|
+
) -> None:
|
|
89
|
+
args = Namespace(service_name="test-service")
|
|
90
|
+
service = Service(
|
|
91
|
+
name="test-service",
|
|
92
|
+
repo_path=str(tmp_path),
|
|
93
|
+
config=ServiceConfig(
|
|
94
|
+
version=0.1,
|
|
95
|
+
service_name="test-service",
|
|
96
|
+
dependencies={
|
|
97
|
+
"redis": Dependency(description="Redis"),
|
|
98
|
+
"postgres": Dependency(description="Postgres"),
|
|
99
|
+
},
|
|
100
|
+
modes={"default": ["redis", "postgres"]},
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
mock_find_matching_service.return_value = service
|
|
104
|
+
|
|
105
|
+
list_dependencies(args)
|
|
106
|
+
|
|
107
|
+
mock_find_matching_service.assert_called_once_with("test-service")
|
|
108
|
+
captured = capsys.readouterr()
|
|
109
|
+
assert "Dependencies of test-service:" in captured.out
|
|
110
|
+
assert "- redis: Redis" in captured.out
|
|
111
|
+
assert "- postgres: Postgres" in captured.out
|
|
@@ -19,7 +19,7 @@ def test_list_running_services(
|
|
|
19
19
|
return_value=str(tmp_path / "code"),
|
|
20
20
|
), mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
|
|
21
21
|
state = State()
|
|
22
|
-
state.
|
|
22
|
+
state.update_started_service("example-service", "default")
|
|
23
23
|
config = {
|
|
24
24
|
"x-sentry-service-config": {
|
|
25
25
|
"version": 0.1,
|
|
@@ -47,7 +47,7 @@ def test_list_running_services(
|
|
|
47
47
|
|
|
48
48
|
assert (
|
|
49
49
|
captured.out
|
|
50
|
-
== f"Running services:\n- example-service\n status: running\n location: {tmp_path / 'code' / 'example-service'}\n"
|
|
50
|
+
== f"Running services:\n- example-service\n modes: ['default']\n status: running\n location: {tmp_path / 'code' / 'example-service'}\n"
|
|
51
51
|
)
|
|
52
52
|
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ def test_list_all_services(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -
|
|
|
57
57
|
return_value=str(tmp_path / "code"),
|
|
58
58
|
), mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
|
|
59
59
|
state = State()
|
|
60
|
-
state.
|
|
60
|
+
state.update_started_service("example-service", "default")
|
|
61
61
|
config = {
|
|
62
62
|
"x-sentry-service-config": {
|
|
63
63
|
"version": 0.1,
|
|
@@ -85,5 +85,5 @@ def test_list_all_services(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -
|
|
|
85
85
|
|
|
86
86
|
assert (
|
|
87
87
|
captured.out
|
|
88
|
-
== f"Services installed locally:\n- example-service\n status: running\n location: {tmp_path / 'code' / 'example-service'}\n"
|
|
88
|
+
== f"Services installed locally:\n- example-service\n modes: ['default']\n status: running\n location: {tmp_path / 'code' / 'example-service'}\n"
|
|
89
89
|
)
|