devservices 1.2.2__tar.gz → 1.2.3__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.2.2 → devservices-1.2.3}/PKG-INFO +1 -1
- {devservices-1.2.2 → devservices-1.2.3}/README.md +1 -1
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/down.py +10 -0
- devservices-1.2.3/devservices/commands/purge.py +219 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/up.py +11 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/main.py +6 -1
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/dependencies.py +27 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/docker_compose.py +9 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices.egg-info/PKG-INFO +1 -1
- {devservices-1.2.2 → devservices-1.2.3}/pyproject.toml +1 -1
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_purge.py +144 -0
- devservices-1.2.2/devservices/commands/purge.py +0 -96
- {devservices-1.2.2 → devservices-1.2.3}/LICENSE.md +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/__init__.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/__init__.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/foreground.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/list_dependencies.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/list_services.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/logs.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/reset.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/serve.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/status.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/toggle.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/commands/update.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/configs/service_config.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/constants.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/exceptions.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/__init__.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/check_for_update.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/console.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/devenv.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/docker.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/file_lock.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/git.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/install_binary.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/services.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/state.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices/utils/supervisor.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices.egg-info/SOURCES.txt +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices.egg-info/requires.txt +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/setup.cfg +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/testing/__init__.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/testing/utils.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/__init__.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_down.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_foreground.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_list_dependencies.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_list_services.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_logs.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_reset.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_serve.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_status.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_toggle.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_up.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/commands/test_update.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/configs/test_service_config.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/conftest.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_check_for_update.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_dependencies.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_docker.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_docker_compose.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_git.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_install_binary.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_services.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_state.py +0 -0
- {devservices-1.2.2 → devservices-1.2.3}/tests/utils/test_supervisor.py +0 -0
|
@@ -33,7 +33,7 @@ NOTE: service-name is an optional parameter. If not provided, devservices will a
|
|
|
33
33
|
The recommended way to install devservices is through a virtualenv in the requirements.txt. Once that is installed and a devservices config file is added, you should be able to run `devservices up` to begin local development.
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
devservices==1.2.
|
|
36
|
+
devservices==1.2.3
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### 2. Add devservices config files
|
|
@@ -8,6 +8,7 @@ from argparse import ArgumentParser
|
|
|
8
8
|
from argparse import Namespace
|
|
9
9
|
|
|
10
10
|
from sentry_sdk import capture_exception
|
|
11
|
+
from sentry_sdk import logger as sentry_logger
|
|
11
12
|
|
|
12
13
|
from devservices.constants import CONFIG_FILE_NAME
|
|
13
14
|
from devservices.constants import DEPENDENCY_CONFIG_VERSION
|
|
@@ -116,6 +117,15 @@ def down(args: Namespace) -> None:
|
|
|
116
117
|
== DependencyType.SUPERVISOR
|
|
117
118
|
]
|
|
118
119
|
|
|
120
|
+
sentry_logger.info(
|
|
121
|
+
"Stopping service",
|
|
122
|
+
extra={
|
|
123
|
+
"service_name": service.name,
|
|
124
|
+
"exclude_local": exclude_local,
|
|
125
|
+
"active_modes": list(active_modes),
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
|
|
119
129
|
with Status(
|
|
120
130
|
lambda: console.warning(f"Stopping {service.name}"),
|
|
121
131
|
) as status:
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from argparse import _SubParsersAction
|
|
6
|
+
from argparse import ArgumentParser
|
|
7
|
+
from argparse import Namespace
|
|
8
|
+
|
|
9
|
+
from devservices.configs.service_config import load_service_config_from_file
|
|
10
|
+
from devservices.constants import DEPENDENCY_CONFIG_VERSION
|
|
11
|
+
from devservices.constants import DEVSERVICES_CACHE_DIR
|
|
12
|
+
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR
|
|
13
|
+
from devservices.constants import DEVSERVICES_ORCHESTRATOR_LABEL
|
|
14
|
+
from devservices.constants import DOCKER_NETWORK_NAME
|
|
15
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
16
|
+
from devservices.exceptions import ConfigParseError
|
|
17
|
+
from devservices.exceptions import ConfigValidationError
|
|
18
|
+
from devservices.exceptions import DockerDaemonNotRunningError
|
|
19
|
+
from devservices.exceptions import DockerError
|
|
20
|
+
from devservices.utils.console import Console
|
|
21
|
+
from devservices.utils.console import Status
|
|
22
|
+
from devservices.utils.docker import get_matching_containers
|
|
23
|
+
from devservices.utils.docker import get_matching_networks
|
|
24
|
+
from devservices.utils.docker import get_volumes_for_containers
|
|
25
|
+
from devservices.utils.docker import remove_docker_resources
|
|
26
|
+
from devservices.utils.docker import stop_containers
|
|
27
|
+
from devservices.utils.state import State
|
|
28
|
+
from devservices.utils.state import StateTables
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
32
|
+
parser = subparsers.add_parser("purge", help="Purge the local devservices cache")
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"service_name",
|
|
35
|
+
nargs="?",
|
|
36
|
+
help="Service name to purge (optional, purges all if not specified)",
|
|
37
|
+
default=None,
|
|
38
|
+
)
|
|
39
|
+
parser.set_defaults(func=purge)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_service_cache_paths(service_name: str) -> list[str]:
|
|
43
|
+
"""Find cache directory paths for a given service name."""
|
|
44
|
+
|
|
45
|
+
cache_paths: list[str] = []
|
|
46
|
+
dependencies_cache_dir = os.path.join(
|
|
47
|
+
DEVSERVICES_DEPENDENCIES_CACHE_DIR, DEPENDENCY_CONFIG_VERSION
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if not os.path.exists(dependencies_cache_dir):
|
|
51
|
+
return cache_paths
|
|
52
|
+
|
|
53
|
+
for repo_name in os.listdir(dependencies_cache_dir):
|
|
54
|
+
repo_path = os.path.join(dependencies_cache_dir, repo_name)
|
|
55
|
+
if not os.path.isdir(repo_path):
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
service_config = load_service_config_from_file(repo_path)
|
|
60
|
+
if service_config.service_name == service_name:
|
|
61
|
+
cache_paths.append(repo_path)
|
|
62
|
+
except (ConfigNotFoundError, ConfigParseError, ConfigValidationError):
|
|
63
|
+
# Skip invalid configs
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
return cache_paths
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def purge(args: Namespace) -> None:
|
|
70
|
+
"""Purge the local devservices state and cache and remove all devservices containers and volumes."""
|
|
71
|
+
console = Console()
|
|
72
|
+
service_name = getattr(args, "service_name", None)
|
|
73
|
+
|
|
74
|
+
if service_name:
|
|
75
|
+
_purge_service(service_name, console)
|
|
76
|
+
else:
|
|
77
|
+
_purge_all(console)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _purge_service(service_name: str, console: Console) -> None:
|
|
81
|
+
"""Purge a specific service."""
|
|
82
|
+
state = State()
|
|
83
|
+
|
|
84
|
+
# Warn user about potential dependency issues
|
|
85
|
+
if not console.confirm(
|
|
86
|
+
f"WARNING: Purging {service_name} may introduce issues with the dependency tree.\n"
|
|
87
|
+
"Other services that depend on this service may stop working correctly.\n"
|
|
88
|
+
"Do you want to continue?"
|
|
89
|
+
):
|
|
90
|
+
console.info("Purge cancelled.")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
state.remove_service_entry(service_name, StateTables.SERVICE_RUNTIME)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
service_containers = get_matching_containers(
|
|
97
|
+
[
|
|
98
|
+
DEVSERVICES_ORCHESTRATOR_LABEL,
|
|
99
|
+
f"com.docker.compose.service={service_name}",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
except DockerDaemonNotRunningError as e:
|
|
103
|
+
console.warning(str(e))
|
|
104
|
+
service_containers = []
|
|
105
|
+
except DockerError as de:
|
|
106
|
+
console.failure(f"Failed to get containers for {service_name}: {de.stderr}")
|
|
107
|
+
exit(1)
|
|
108
|
+
|
|
109
|
+
if len(service_containers) == 0:
|
|
110
|
+
console.warning(f"No containers found for {service_name}")
|
|
111
|
+
else:
|
|
112
|
+
try:
|
|
113
|
+
service_volumes = get_volumes_for_containers(service_containers)
|
|
114
|
+
except DockerError as e:
|
|
115
|
+
console.failure(f"Failed to get volumes for {service_name}: {e.stderr}")
|
|
116
|
+
exit(1)
|
|
117
|
+
|
|
118
|
+
with Status(
|
|
119
|
+
lambda: console.warning(f"Stopping {service_name} containers"),
|
|
120
|
+
lambda: console.success(f"{service_name} containers have been stopped"),
|
|
121
|
+
):
|
|
122
|
+
try:
|
|
123
|
+
stop_containers(service_containers, should_remove=True)
|
|
124
|
+
except DockerError as e:
|
|
125
|
+
console.failure(f"Failed to stop {service_name} containers: {e.stderr}")
|
|
126
|
+
exit(1)
|
|
127
|
+
|
|
128
|
+
console.warning(f"Removing {service_name} docker volumes")
|
|
129
|
+
if len(service_volumes) == 0:
|
|
130
|
+
console.success(f"No volumes found for {service_name}")
|
|
131
|
+
else:
|
|
132
|
+
try:
|
|
133
|
+
remove_docker_resources("volume", list(service_volumes))
|
|
134
|
+
console.success(f"{service_name} volumes removed")
|
|
135
|
+
except DockerError as e:
|
|
136
|
+
console.failure(f"Failed to remove {service_name} volumes: {e.stderr}")
|
|
137
|
+
|
|
138
|
+
cache_paths = _get_service_cache_paths(service_name)
|
|
139
|
+
if cache_paths:
|
|
140
|
+
console.warning(f"Removing cache for {service_name}")
|
|
141
|
+
for cache_path in cache_paths:
|
|
142
|
+
try:
|
|
143
|
+
shutil.rmtree(cache_path)
|
|
144
|
+
except PermissionError as e:
|
|
145
|
+
console.failure(f"Failed to remove cache at {cache_path}: {e}")
|
|
146
|
+
exit(1)
|
|
147
|
+
console.success(f"Cache for {service_name} has been removed")
|
|
148
|
+
else:
|
|
149
|
+
console.success(f"No cache found for {service_name}")
|
|
150
|
+
|
|
151
|
+
console.success(f"{service_name} has been purged")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _purge_all(console: Console) -> None:
|
|
155
|
+
"""Purge all devservices state, cache, containers, volumes, and networks."""
|
|
156
|
+
if os.path.exists(DEVSERVICES_CACHE_DIR):
|
|
157
|
+
try:
|
|
158
|
+
shutil.rmtree(DEVSERVICES_CACHE_DIR)
|
|
159
|
+
except PermissionError as e:
|
|
160
|
+
console.failure(f"Failed to purge cache: {e}")
|
|
161
|
+
exit(1)
|
|
162
|
+
state = State()
|
|
163
|
+
state.clear_state()
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
devservices_containers = get_matching_containers(
|
|
167
|
+
[DEVSERVICES_ORCHESTRATOR_LABEL]
|
|
168
|
+
)
|
|
169
|
+
except DockerDaemonNotRunningError as e:
|
|
170
|
+
console.warning(str(e))
|
|
171
|
+
return
|
|
172
|
+
except DockerError as de:
|
|
173
|
+
console.failure(f"Failed to get devservices containers {de.stderr}")
|
|
174
|
+
exit(1)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
devservices_volumes = get_volumes_for_containers(devservices_containers)
|
|
178
|
+
except DockerError as e:
|
|
179
|
+
console.failure(f"Failed to get devservices volumes {e.stderr}")
|
|
180
|
+
exit(1)
|
|
181
|
+
|
|
182
|
+
with Status(
|
|
183
|
+
lambda: console.warning("Stopping all devservices containers"),
|
|
184
|
+
lambda: console.success("All devservices containers have been stopped"),
|
|
185
|
+
):
|
|
186
|
+
try:
|
|
187
|
+
stop_containers(devservices_containers, should_remove=True)
|
|
188
|
+
except DockerError as e:
|
|
189
|
+
console.failure(f"Failed to stop devservices containers {e.stderr}")
|
|
190
|
+
exit(1)
|
|
191
|
+
|
|
192
|
+
console.warning("Removing any devservices docker volumes")
|
|
193
|
+
if len(devservices_volumes) == 0:
|
|
194
|
+
console.success("No devservices volumes found to remove")
|
|
195
|
+
else:
|
|
196
|
+
try:
|
|
197
|
+
remove_docker_resources("volume", list(devservices_volumes))
|
|
198
|
+
console.success("All devservices volumes removed")
|
|
199
|
+
except DockerError as e:
|
|
200
|
+
# We don't want to exit here since we still want to try to remove the networks
|
|
201
|
+
console.failure(f"Failed to remove devservices volumes {e.stderr}")
|
|
202
|
+
|
|
203
|
+
console.warning("Removing any devservices networks")
|
|
204
|
+
try:
|
|
205
|
+
devservices_networks = get_matching_networks(DOCKER_NETWORK_NAME)
|
|
206
|
+
except DockerError as e:
|
|
207
|
+
console.failure(f"Failed to get devservices networks {e.stderr}")
|
|
208
|
+
exit(1)
|
|
209
|
+
if len(devservices_networks) == 0:
|
|
210
|
+
console.success("No devservices networks found to remove")
|
|
211
|
+
else:
|
|
212
|
+
try:
|
|
213
|
+
remove_docker_resources("network", devservices_networks)
|
|
214
|
+
console.success("All devservices networks removed")
|
|
215
|
+
except DockerError as e:
|
|
216
|
+
console.failure(f"Failed to remove devservices networks {e.stderr}")
|
|
217
|
+
exit(1)
|
|
218
|
+
|
|
219
|
+
console.success("The local devservices cache and state has been purged")
|
|
@@ -8,6 +8,7 @@ from argparse import ArgumentParser
|
|
|
8
8
|
from argparse import Namespace
|
|
9
9
|
|
|
10
10
|
from sentry_sdk import capture_exception
|
|
11
|
+
from sentry_sdk import logger as sentry_logger
|
|
11
12
|
from sentry_sdk import set_context
|
|
12
13
|
from sentry_sdk import start_span
|
|
13
14
|
|
|
@@ -94,6 +95,16 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
|
|
|
94
95
|
mode = args.mode
|
|
95
96
|
exclude_local = getattr(args, "exclude_local", False)
|
|
96
97
|
|
|
98
|
+
sentry_logger.info(
|
|
99
|
+
"Starting service",
|
|
100
|
+
extra={
|
|
101
|
+
"service_name": service.name,
|
|
102
|
+
"mode": mode,
|
|
103
|
+
"exclude_local": exclude_local,
|
|
104
|
+
"available_modes": list(modes.keys()),
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
97
108
|
state = State()
|
|
98
109
|
|
|
99
110
|
with Status(
|
|
@@ -16,6 +16,7 @@ from sentry_sdk import set_tag
|
|
|
16
16
|
from sentry_sdk import set_user
|
|
17
17
|
from sentry_sdk import start_transaction
|
|
18
18
|
from sentry_sdk.integrations.argv import ArgvIntegration
|
|
19
|
+
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
19
20
|
from sentry_sdk.types import Event
|
|
20
21
|
from sentry_sdk.types import Hint
|
|
21
22
|
|
|
@@ -79,7 +80,11 @@ if not disable_sentry:
|
|
|
79
80
|
dsn="https://56470da7302c16e83141f62f88e46449@o1.ingest.us.sentry.io/4507946704961536",
|
|
80
81
|
traces_sample_rate=1.0,
|
|
81
82
|
profiles_sample_rate=1.0,
|
|
82
|
-
integrations=[
|
|
83
|
+
integrations=[
|
|
84
|
+
ArgvIntegration(),
|
|
85
|
+
LoggingIntegration(sentry_logs_level=logging.DEBUG),
|
|
86
|
+
],
|
|
87
|
+
enable_logs=True,
|
|
83
88
|
environment=sentry_environment,
|
|
84
89
|
before_send=before_send_error,
|
|
85
90
|
before_send_transaction=before_send_transaction,
|
|
@@ -14,6 +14,7 @@ from typing import TextIO
|
|
|
14
14
|
from typing import TypeGuard
|
|
15
15
|
|
|
16
16
|
from sentry_sdk import capture_message
|
|
17
|
+
from sentry_sdk import logger as sentry_logger
|
|
17
18
|
from sentry_sdk import set_context
|
|
18
19
|
|
|
19
20
|
from devservices.configs.service_config import Dependency
|
|
@@ -417,6 +418,16 @@ def install_dependency(dependency: RemoteConfig) -> set[InstalledRemoteDependenc
|
|
|
417
418
|
dependency.repo_name,
|
|
418
419
|
)
|
|
419
420
|
|
|
421
|
+
sentry_logger.info(
|
|
422
|
+
"Installing dependency",
|
|
423
|
+
extra={
|
|
424
|
+
"repo_name": dependency.repo_name,
|
|
425
|
+
"repo_link": dependency.repo_link,
|
|
426
|
+
"branch": dependency.branch,
|
|
427
|
+
"mode": dependency.mode,
|
|
428
|
+
},
|
|
429
|
+
)
|
|
430
|
+
|
|
420
431
|
os.makedirs(DEVSERVICES_DEPENDENCIES_CACHE_DIR, exist_ok=True)
|
|
421
432
|
|
|
422
433
|
# Ensure that only one process is installing a specific dependency at a time
|
|
@@ -498,6 +509,14 @@ def _update_dependency(
|
|
|
498
509
|
dependency: RemoteConfig,
|
|
499
510
|
dependency_repo_dir: str,
|
|
500
511
|
) -> None:
|
|
512
|
+
sentry_logger.info(
|
|
513
|
+
"Updating dependency",
|
|
514
|
+
extra={
|
|
515
|
+
"repo_name": dependency.repo_name,
|
|
516
|
+
"repo_link": dependency.repo_link,
|
|
517
|
+
"branch": dependency.branch,
|
|
518
|
+
},
|
|
519
|
+
)
|
|
501
520
|
git_config_manager = GitConfigManager(
|
|
502
521
|
dependency_repo_dir,
|
|
503
522
|
DEPENDENCY_GIT_PARTIAL_CLONE_CONFIG_OPTIONS,
|
|
@@ -580,6 +599,14 @@ def _checkout_dependency(
|
|
|
580
599
|
dependency: RemoteConfig,
|
|
581
600
|
dependency_repo_dir: str,
|
|
582
601
|
) -> None:
|
|
602
|
+
sentry_logger.info(
|
|
603
|
+
"Checking out dependency",
|
|
604
|
+
extra={
|
|
605
|
+
"repo_name": dependency.repo_name,
|
|
606
|
+
"repo_link": dependency.repo_link,
|
|
607
|
+
"branch": dependency.branch,
|
|
608
|
+
},
|
|
609
|
+
)
|
|
583
610
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
584
611
|
try:
|
|
585
612
|
_run_command(
|
|
@@ -12,6 +12,7 @@ from typing import cast
|
|
|
12
12
|
from typing import NamedTuple
|
|
13
13
|
|
|
14
14
|
from packaging import version
|
|
15
|
+
from sentry_sdk import logger as sentry_logger
|
|
15
16
|
|
|
16
17
|
from devservices.configs.service_config import load_service_config_from_file
|
|
17
18
|
from devservices.constants import CONFIG_FILE_NAME
|
|
@@ -306,6 +307,14 @@ def run_cmd(
|
|
|
306
307
|
console = Console()
|
|
307
308
|
cmd_pretty = shlex.join(cmd)
|
|
308
309
|
|
|
310
|
+
sentry_logger.info(
|
|
311
|
+
"Running docker compose command",
|
|
312
|
+
extra={
|
|
313
|
+
"command": cmd_pretty,
|
|
314
|
+
"max_retries": retries,
|
|
315
|
+
},
|
|
316
|
+
)
|
|
317
|
+
|
|
309
318
|
proc = None
|
|
310
319
|
retries += 1 # initial try
|
|
311
320
|
|
|
@@ -552,3 +552,147 @@ def test_purge_with_cache_and_state_and_containers_with_networks_and_volumes(
|
|
|
552
552
|
mock.call("network", ["abc", "def", "ghe"]),
|
|
553
553
|
]
|
|
554
554
|
)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
@mock.patch("devservices.commands.purge.Console.confirm")
|
|
558
|
+
@mock.patch("devservices.commands.purge.get_matching_containers")
|
|
559
|
+
@mock.patch("devservices.commands.purge.get_volumes_for_containers")
|
|
560
|
+
@mock.patch("devservices.commands.purge.stop_containers")
|
|
561
|
+
@mock.patch("devservices.commands.purge.remove_docker_resources")
|
|
562
|
+
@mock.patch("devservices.commands.purge._get_service_cache_paths")
|
|
563
|
+
def test_purge_specific_service(
|
|
564
|
+
mock_get_service_cache_paths: mock.Mock,
|
|
565
|
+
mock_remove_docker_resources: mock.Mock,
|
|
566
|
+
mock_stop_containers: mock.Mock,
|
|
567
|
+
mock_get_volumes_for_containers: mock.Mock,
|
|
568
|
+
mock_get_matching_containers: mock.Mock,
|
|
569
|
+
mock_console_confirm: mock.Mock,
|
|
570
|
+
tmp_path: Path,
|
|
571
|
+
) -> None:
|
|
572
|
+
"""Test that purging a specific service removes only that service's containers, volumes, and cache."""
|
|
573
|
+
mock_console_confirm.return_value = True
|
|
574
|
+
mock_get_matching_containers.return_value = [
|
|
575
|
+
"kafka-container-1",
|
|
576
|
+
"kafka-container-2",
|
|
577
|
+
]
|
|
578
|
+
mock_get_volumes_for_containers.return_value = ["kafka-volume-1", "kafka-volume-2"]
|
|
579
|
+
cache_path = tmp_path / "dependencies" / "v1" / "kafka-repo"
|
|
580
|
+
cache_path.mkdir(parents=True, exist_ok=True)
|
|
581
|
+
mock_get_service_cache_paths.return_value = [str(cache_path)]
|
|
582
|
+
|
|
583
|
+
with (
|
|
584
|
+
mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")),
|
|
585
|
+
mock.patch(
|
|
586
|
+
"devservices.utils.docker.check_docker_daemon_running", return_value=None
|
|
587
|
+
),
|
|
588
|
+
):
|
|
589
|
+
state = State()
|
|
590
|
+
# Don't add kafka to STARTED_SERVICES - it should be stopped before purging
|
|
591
|
+
# Add redis to verify it's not affected by kafka purge
|
|
592
|
+
state.update_service_entry("redis", "default", StateTables.STARTED_SERVICES)
|
|
593
|
+
|
|
594
|
+
assert state.get_service_entries(StateTables.STARTED_SERVICES) == ["redis"]
|
|
595
|
+
assert cache_path.exists()
|
|
596
|
+
|
|
597
|
+
purge(Namespace(service_name="kafka"))
|
|
598
|
+
|
|
599
|
+
# redis should still be in state (unaffected)
|
|
600
|
+
assert state.get_service_entries(StateTables.STARTED_SERVICES) == ["redis"]
|
|
601
|
+
# Cache path should be removed
|
|
602
|
+
assert not cache_path.exists()
|
|
603
|
+
|
|
604
|
+
# Should filter containers by service name
|
|
605
|
+
mock_get_matching_containers.assert_called_once_with(
|
|
606
|
+
[
|
|
607
|
+
DEVSERVICES_ORCHESTRATOR_LABEL,
|
|
608
|
+
"com.docker.compose.service=kafka",
|
|
609
|
+
]
|
|
610
|
+
)
|
|
611
|
+
mock_get_volumes_for_containers.assert_called_once_with(
|
|
612
|
+
["kafka-container-1", "kafka-container-2"]
|
|
613
|
+
)
|
|
614
|
+
mock_stop_containers.assert_called_once_with(
|
|
615
|
+
["kafka-container-1", "kafka-container-2"], should_remove=True
|
|
616
|
+
)
|
|
617
|
+
mock_remove_docker_resources.assert_called_once_with(
|
|
618
|
+
"volume", ["kafka-volume-1", "kafka-volume-2"]
|
|
619
|
+
)
|
|
620
|
+
mock_get_service_cache_paths.assert_called_once_with("kafka")
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@mock.patch("devservices.commands.purge.Console.confirm")
|
|
624
|
+
@mock.patch("devservices.commands.purge.get_matching_containers")
|
|
625
|
+
@mock.patch("devservices.commands.purge._get_service_cache_paths")
|
|
626
|
+
def test_purge_specific_service_no_containers(
|
|
627
|
+
mock_get_service_cache_paths: mock.Mock,
|
|
628
|
+
mock_get_matching_containers: mock.Mock,
|
|
629
|
+
mock_console_confirm: mock.Mock,
|
|
630
|
+
capsys: pytest.CaptureFixture[str],
|
|
631
|
+
tmp_path: Path,
|
|
632
|
+
) -> None:
|
|
633
|
+
"""Test that purging a service with no containers or cache still removes it from state."""
|
|
634
|
+
mock_console_confirm.return_value = True
|
|
635
|
+
mock_get_matching_containers.return_value = []
|
|
636
|
+
mock_get_service_cache_paths.return_value = []
|
|
637
|
+
|
|
638
|
+
with (
|
|
639
|
+
mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")),
|
|
640
|
+
mock.patch(
|
|
641
|
+
"devservices.utils.docker.check_docker_daemon_running", return_value=None
|
|
642
|
+
),
|
|
643
|
+
):
|
|
644
|
+
state = State()
|
|
645
|
+
# Don't add kafka to STARTED_SERVICES - it should be stopped before purging
|
|
646
|
+
|
|
647
|
+
args = Namespace(service_name="kafka")
|
|
648
|
+
purge(args)
|
|
649
|
+
|
|
650
|
+
# State should remain empty (kafka was never added)
|
|
651
|
+
assert state.get_service_entries(StateTables.STARTED_SERVICES) == []
|
|
652
|
+
|
|
653
|
+
captured = capsys.readouterr()
|
|
654
|
+
assert "No containers found for kafka" in captured.out
|
|
655
|
+
assert "No cache found for kafka" in captured.out
|
|
656
|
+
assert "kafka has been purged" in captured.out
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
@mock.patch("devservices.commands.purge.Console.confirm")
|
|
660
|
+
@mock.patch("devservices.commands.purge.get_matching_containers")
|
|
661
|
+
@mock.patch("devservices.commands.purge._get_service_cache_paths")
|
|
662
|
+
def test_purge_specific_service_cancelled_by_user(
|
|
663
|
+
mock_get_service_cache_paths: mock.Mock,
|
|
664
|
+
mock_get_matching_containers: mock.Mock,
|
|
665
|
+
mock_console_confirm: mock.Mock,
|
|
666
|
+
capsys: pytest.CaptureFixture[str],
|
|
667
|
+
tmp_path: Path,
|
|
668
|
+
) -> None:
|
|
669
|
+
"""Test that purging a service can be cancelled by the user."""
|
|
670
|
+
mock_console_confirm.return_value = False
|
|
671
|
+
mock_get_matching_containers.return_value = []
|
|
672
|
+
mock_get_service_cache_paths.return_value = []
|
|
673
|
+
|
|
674
|
+
with (
|
|
675
|
+
mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")),
|
|
676
|
+
mock.patch(
|
|
677
|
+
"devservices.utils.docker.check_docker_daemon_running", return_value=None
|
|
678
|
+
),
|
|
679
|
+
):
|
|
680
|
+
state = State()
|
|
681
|
+
# Add kafka to state
|
|
682
|
+
state.update_service_entry("kafka", "default", StateTables.STARTED_SERVICES)
|
|
683
|
+
|
|
684
|
+
args = Namespace(service_name="kafka")
|
|
685
|
+
purge(args)
|
|
686
|
+
|
|
687
|
+
# Service should still be in state (purge was cancelled)
|
|
688
|
+
assert state.get_service_entries(StateTables.STARTED_SERVICES) == ["kafka"]
|
|
689
|
+
|
|
690
|
+
# Should have prompted user
|
|
691
|
+
mock_console_confirm.assert_called_once()
|
|
692
|
+
|
|
693
|
+
# Should not have attempted to get containers
|
|
694
|
+
mock_get_matching_containers.assert_not_called()
|
|
695
|
+
mock_get_service_cache_paths.assert_not_called()
|
|
696
|
+
|
|
697
|
+
captured = capsys.readouterr()
|
|
698
|
+
assert "Purge cancelled." in captured.out
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import shutil
|
|
5
|
-
from argparse import _SubParsersAction
|
|
6
|
-
from argparse import ArgumentParser
|
|
7
|
-
from argparse import Namespace
|
|
8
|
-
|
|
9
|
-
from devservices.constants import DEVSERVICES_CACHE_DIR
|
|
10
|
-
from devservices.constants import DEVSERVICES_ORCHESTRATOR_LABEL
|
|
11
|
-
from devservices.constants import DOCKER_NETWORK_NAME
|
|
12
|
-
from devservices.exceptions import DockerDaemonNotRunningError
|
|
13
|
-
from devservices.exceptions import DockerError
|
|
14
|
-
from devservices.utils.console import Console
|
|
15
|
-
from devservices.utils.console import Status
|
|
16
|
-
from devservices.utils.docker import get_matching_containers
|
|
17
|
-
from devservices.utils.docker import get_matching_networks
|
|
18
|
-
from devservices.utils.docker import get_volumes_for_containers
|
|
19
|
-
from devservices.utils.docker import remove_docker_resources
|
|
20
|
-
from devservices.utils.docker import stop_containers
|
|
21
|
-
from devservices.utils.state import State
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
25
|
-
parser = subparsers.add_parser("purge", help="Purge the local devservices cache")
|
|
26
|
-
parser.set_defaults(func=purge)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def purge(_args: Namespace) -> None:
|
|
30
|
-
"""Purge the local devservices state and cache and remove all devservices containers and volumes."""
|
|
31
|
-
console = Console()
|
|
32
|
-
|
|
33
|
-
if os.path.exists(DEVSERVICES_CACHE_DIR):
|
|
34
|
-
try:
|
|
35
|
-
shutil.rmtree(DEVSERVICES_CACHE_DIR)
|
|
36
|
-
except PermissionError as e:
|
|
37
|
-
console.failure(f"Failed to purge cache: {e}")
|
|
38
|
-
exit(1)
|
|
39
|
-
state = State()
|
|
40
|
-
state.clear_state()
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
devservices_containers = get_matching_containers(
|
|
44
|
-
[DEVSERVICES_ORCHESTRATOR_LABEL]
|
|
45
|
-
)
|
|
46
|
-
except DockerDaemonNotRunningError as e:
|
|
47
|
-
console.warning(str(e))
|
|
48
|
-
return
|
|
49
|
-
except DockerError as de:
|
|
50
|
-
console.failure(f"Failed to get devservices containers {de.stderr}")
|
|
51
|
-
exit(1)
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
devservices_volumes = get_volumes_for_containers(devservices_containers)
|
|
55
|
-
except DockerError as e:
|
|
56
|
-
console.failure(f"Failed to get devservices volumes {e.stderr}")
|
|
57
|
-
exit(1)
|
|
58
|
-
|
|
59
|
-
with Status(
|
|
60
|
-
lambda: console.warning("Stopping all devservices containers"),
|
|
61
|
-
lambda: console.success("All devservices containers have been stopped"),
|
|
62
|
-
):
|
|
63
|
-
try:
|
|
64
|
-
stop_containers(devservices_containers, should_remove=True)
|
|
65
|
-
except DockerError as e:
|
|
66
|
-
console.failure(f"Failed to stop devservices containers {e.stderr}")
|
|
67
|
-
exit(1)
|
|
68
|
-
|
|
69
|
-
console.warning("Removing any devservices docker volumes")
|
|
70
|
-
if len(devservices_volumes) == 0:
|
|
71
|
-
console.success("No devservices volumes found to remove")
|
|
72
|
-
else:
|
|
73
|
-
try:
|
|
74
|
-
remove_docker_resources("volume", list(devservices_volumes))
|
|
75
|
-
console.success("All devservices volumes removed")
|
|
76
|
-
except DockerError as e:
|
|
77
|
-
# We don't want to exit here since we still want to try to remove the networks
|
|
78
|
-
console.failure(f"Failed to remove devservices volumes {e.stderr}")
|
|
79
|
-
|
|
80
|
-
console.warning("Removing any devservices networks")
|
|
81
|
-
try:
|
|
82
|
-
devservices_networks = get_matching_networks(DOCKER_NETWORK_NAME)
|
|
83
|
-
except DockerError as e:
|
|
84
|
-
console.failure(f"Failed to get devservices networks {e.stderr}")
|
|
85
|
-
exit(1)
|
|
86
|
-
if len(devservices_networks) == 0:
|
|
87
|
-
console.success("No devservices networks found to remove")
|
|
88
|
-
else:
|
|
89
|
-
try:
|
|
90
|
-
remove_docker_resources("network", devservices_networks)
|
|
91
|
-
console.success("All devservices networks removed")
|
|
92
|
-
except DockerError as e:
|
|
93
|
-
console.failure(f"Failed to remove devservices networks {e.stderr}")
|
|
94
|
-
exit(1)
|
|
95
|
-
|
|
96
|
-
console.success("The local devservices cache and state has been purged")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|