devservices 1.0.6__tar.gz → 1.0.8__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.6 → devservices-1.0.8}/PKG-INFO +3 -1
- {devservices-1.0.6 → devservices-1.0.8}/README.md +1 -1
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/down.py +4 -4
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/logs.py +1 -1
- devservices-1.0.8/devservices/commands/purge.py +94 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/status.py +19 -9
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/up.py +34 -8
- {devservices-1.0.6 → devservices-1.0.8}/devservices/configs/service_config.py +1 -1
- {devservices-1.0.6 → devservices-1.0.8}/devservices/constants.py +2 -1
- {devservices-1.0.6 → devservices-1.0.8}/devservices/exceptions.py +11 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/main.py +0 -9
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/dependencies.py +49 -0
- devservices-1.0.8/devservices/utils/docker.py +219 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/docker_compose.py +44 -5
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/file_lock.py +1 -1
- {devservices-1.0.6 → devservices-1.0.8}/devservices.egg-info/PKG-INFO +3 -1
- {devservices-1.0.6 → devservices-1.0.8}/devservices.egg-info/requires.txt +2 -0
- {devservices-1.0.6 → devservices-1.0.8}/pyproject.toml +3 -1
- devservices-1.0.8/tests/commands/test_purge.py +517 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_status.py +75 -1
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_up.py +275 -2
- {devservices-1.0.6 → devservices-1.0.8}/tests/utils/test_dependencies.py +48 -0
- devservices-1.0.8/tests/utils/test_docker.py +493 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/utils/test_docker_compose.py +165 -99
- devservices-1.0.6/devservices/commands/purge.py +0 -78
- devservices-1.0.6/devservices/utils/docker.py +0 -88
- devservices-1.0.6/tests/commands/test_purge.py +0 -214
- devservices-1.0.6/tests/utils/test_docker.py +0 -182
- {devservices-1.0.6 → devservices-1.0.8}/LICENSE.md +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/__init__.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/__init__.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/list_dependencies.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/list_services.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/commands/update.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/__init__.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/check_for_update.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/console.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/devenv.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/install_binary.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/services.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices/utils/state.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices.egg-info/SOURCES.txt +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/setup.cfg +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/testing/__init__.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/testing/utils.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/__init__.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_down.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_list_dependencies.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_list_services.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_logs.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/commands/test_update.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/configs/test_service_config.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/conftest.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/utils/test_check_for_update.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/utils/test_install_binary.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/utils/test_services.py +0 -0
- {devservices-1.0.6 → devservices-1.0.8}/tests/utils/test_state.py +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devservices
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8
|
|
4
4
|
Requires-Python: >=3.10
|
|
5
5
|
License-File: LICENSE.md
|
|
6
6
|
Requires-Dist: pyyaml
|
|
7
7
|
Requires-Dist: sentry-devenv
|
|
8
|
+
Requires-Dist: sentry-sdk
|
|
9
|
+
Requires-Dist: packaging
|
|
8
10
|
Provides-Extra: dev
|
|
9
11
|
Requires-Dist: black; extra == "dev"
|
|
10
12
|
Requires-Dist: mypy; extra == "dev"
|
|
@@ -14,7 +14,6 @@ from devservices.constants import DEPENDENCY_CONFIG_VERSION
|
|
|
14
14
|
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR
|
|
15
15
|
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
16
16
|
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
17
|
-
from devservices.constants import DOCKER_COMPOSE_COMMAND_LENGTH
|
|
18
17
|
from devservices.exceptions import ConfigError
|
|
19
18
|
from devservices.exceptions import DependencyError
|
|
20
19
|
from devservices.exceptions import DockerComposeError
|
|
@@ -24,6 +23,7 @@ from devservices.utils.console import Status
|
|
|
24
23
|
from devservices.utils.dependencies import get_non_shared_remote_dependencies
|
|
25
24
|
from devservices.utils.dependencies import install_and_verify_dependencies
|
|
26
25
|
from devservices.utils.dependencies import InstalledRemoteDependency
|
|
26
|
+
from devservices.utils.docker_compose import DockerComposeCommand
|
|
27
27
|
from devservices.utils.docker_compose import get_docker_compose_commands_to_run
|
|
28
28
|
from devservices.utils.docker_compose import run_cmd
|
|
29
29
|
from devservices.utils.services import find_matching_service
|
|
@@ -111,12 +111,12 @@ def down(args: Namespace) -> None:
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def _bring_down_dependency(
|
|
114
|
-
cmd:
|
|
114
|
+
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
|
|
115
115
|
) -> subprocess.CompletedProcess[str]:
|
|
116
116
|
# TODO: Get rid of these constants, we need a smarter way to determine the containers being brought down
|
|
117
|
-
for dependency in cmd
|
|
117
|
+
for dependency in cmd.services:
|
|
118
118
|
status.info(f"Stopping {dependency}")
|
|
119
|
-
return run_cmd(cmd, current_env)
|
|
119
|
+
return run_cmd(cmd.full_command, current_env)
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def _down(
|
|
@@ -114,7 +114,7 @@ def _logs(
|
|
|
114
114
|
|
|
115
115
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
116
116
|
futures = [
|
|
117
|
-
executor.submit(run_cmd, cmd, current_env)
|
|
117
|
+
executor.submit(run_cmd, cmd.full_command, current_env)
|
|
118
118
|
for cmd in docker_compose_commands
|
|
119
119
|
]
|
|
120
120
|
for future in concurrent.futures.as_completed(futures):
|
|
@@ -0,0 +1,94 @@
|
|
|
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(DEVSERVICES_ORCHESTRATOR_LABEL)
|
|
44
|
+
except DockerDaemonNotRunningError as e:
|
|
45
|
+
console.warning(str(e))
|
|
46
|
+
return
|
|
47
|
+
except DockerError as de:
|
|
48
|
+
console.failure(f"Failed to get devservices containers {de.stderr}")
|
|
49
|
+
exit(1)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
devservices_volumes = get_volumes_for_containers(devservices_containers)
|
|
53
|
+
except DockerError as e:
|
|
54
|
+
console.failure(f"Failed to get devservices volumes {e.stderr}")
|
|
55
|
+
exit(1)
|
|
56
|
+
|
|
57
|
+
with Status(
|
|
58
|
+
lambda: console.warning("Stopping all devservices containers"),
|
|
59
|
+
lambda: console.success("All devservices containers have been stopped"),
|
|
60
|
+
):
|
|
61
|
+
try:
|
|
62
|
+
stop_containers(devservices_containers, should_remove=True)
|
|
63
|
+
except DockerError as e:
|
|
64
|
+
console.failure(f"Failed to stop devservices containers {e.stderr}")
|
|
65
|
+
exit(1)
|
|
66
|
+
|
|
67
|
+
console.warning("Removing any devservices docker volumes")
|
|
68
|
+
if len(devservices_volumes) == 0:
|
|
69
|
+
console.success("No devservices volumes found to remove")
|
|
70
|
+
else:
|
|
71
|
+
try:
|
|
72
|
+
remove_docker_resources("volume", list(devservices_volumes))
|
|
73
|
+
console.success("All devservices volumes removed")
|
|
74
|
+
except DockerError as e:
|
|
75
|
+
# We don't want to exit here since we still want to try to remove the networks
|
|
76
|
+
console.failure(f"Failed to remove devservices volumes {e.stderr}")
|
|
77
|
+
|
|
78
|
+
console.warning("Removing any devservices networks")
|
|
79
|
+
try:
|
|
80
|
+
devservices_networks = get_matching_networks(DOCKER_NETWORK_NAME)
|
|
81
|
+
except DockerError as e:
|
|
82
|
+
console.failure(f"Failed to get devservices networks {e.stderr}")
|
|
83
|
+
exit(1)
|
|
84
|
+
if len(devservices_networks) == 0:
|
|
85
|
+
console.success("No devservices networks found to remove")
|
|
86
|
+
else:
|
|
87
|
+
try:
|
|
88
|
+
remove_docker_resources("network", devservices_networks)
|
|
89
|
+
console.success("All devservices networks removed")
|
|
90
|
+
except DockerError as e:
|
|
91
|
+
console.failure(f"Failed to remove devservices networks {e.stderr}")
|
|
92
|
+
exit(1)
|
|
93
|
+
|
|
94
|
+
console.success("The local devservices cache and state has been purged")
|
|
@@ -7,6 +7,7 @@ import subprocess
|
|
|
7
7
|
from argparse import _SubParsersAction
|
|
8
8
|
from argparse import ArgumentParser
|
|
9
9
|
from argparse import Namespace
|
|
10
|
+
from collections import namedtuple
|
|
10
11
|
|
|
11
12
|
from sentry_sdk import capture_exception
|
|
12
13
|
|
|
@@ -30,6 +31,9 @@ from devservices.utils.services import Service
|
|
|
30
31
|
LINE_LENGTH = 40
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
ServiceStatus = namedtuple("ServiceStatus", ["name", "formatted_output"])
|
|
35
|
+
|
|
36
|
+
|
|
33
37
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
34
38
|
parser = subparsers.add_parser("status", help="View status of a service")
|
|
35
39
|
parser.add_argument(
|
|
@@ -41,12 +45,12 @@ def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
41
45
|
parser.set_defaults(func=status)
|
|
42
46
|
|
|
43
47
|
|
|
44
|
-
def format_status_output(
|
|
48
|
+
def format_status_output(service_status_json: str) -> list[ServiceStatus]:
|
|
45
49
|
# Docker compose ps is line delimited json, so this constructs this into an array we can use
|
|
46
|
-
service_statuses =
|
|
47
|
-
|
|
48
|
-
output.append("-" * LINE_LENGTH)
|
|
50
|
+
service_statuses = service_status_json.split("\n")[:-1]
|
|
51
|
+
outputs = []
|
|
49
52
|
for service_status in service_statuses:
|
|
53
|
+
output = []
|
|
50
54
|
service = json.loads(service_status)
|
|
51
55
|
name = service["Service"]
|
|
52
56
|
state = service["State"]
|
|
@@ -71,8 +75,9 @@ def format_status_output(status_json: str) -> str:
|
|
|
71
75
|
output.append("No ports exposed")
|
|
72
76
|
|
|
73
77
|
output.append("") # Empty line for readability
|
|
78
|
+
outputs.append(ServiceStatus(name=name, formatted_output="\n".join(output)))
|
|
74
79
|
|
|
75
|
-
return
|
|
80
|
+
return outputs
|
|
76
81
|
|
|
77
82
|
|
|
78
83
|
def status(args: Namespace) -> None:
|
|
@@ -115,10 +120,15 @@ def status(args: Namespace) -> None:
|
|
|
115
120
|
console.warning(f"{service.name} is not running")
|
|
116
121
|
return
|
|
117
122
|
output = f"Service: {service.name}\n\n"
|
|
123
|
+
output += "=" * LINE_LENGTH + "\n"
|
|
124
|
+
formatted_status_outputs = []
|
|
118
125
|
for status_json in status_json_results:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
formatted_status_outputs.extend(format_status_output(status_json.stdout))
|
|
127
|
+
formatted_status_outputs.sort(key=lambda x: x.name)
|
|
128
|
+
for formatted_status_output in formatted_status_outputs:
|
|
129
|
+
output += formatted_status_output[1]
|
|
130
|
+
output += "-" * LINE_LENGTH + "\n"
|
|
131
|
+
console.info(output)
|
|
122
132
|
|
|
123
133
|
|
|
124
134
|
def _status(
|
|
@@ -152,7 +162,7 @@ def _status(
|
|
|
152
162
|
|
|
153
163
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
154
164
|
futures = [
|
|
155
|
-
executor.submit(run_cmd, cmd, current_env)
|
|
165
|
+
executor.submit(run_cmd, cmd.full_command, current_env)
|
|
156
166
|
for cmd in docker_compose_commands
|
|
157
167
|
]
|
|
158
168
|
for future in concurrent.futures.as_completed(futures):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import concurrent.futures
|
|
3
4
|
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
from argparse import _SubParsersAction
|
|
@@ -13,8 +14,8 @@ from devservices.constants import DEPENDENCY_CONFIG_VERSION
|
|
|
13
14
|
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR
|
|
14
15
|
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
15
16
|
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
16
|
-
from devservices.constants import DOCKER_COMPOSE_COMMAND_LENGTH
|
|
17
17
|
from devservices.exceptions import ConfigError
|
|
18
|
+
from devservices.exceptions import ContainerHealthcheckFailedError
|
|
18
19
|
from devservices.exceptions import DependencyError
|
|
19
20
|
from devservices.exceptions import DockerComposeError
|
|
20
21
|
from devservices.exceptions import ModeDoesNotExistError
|
|
@@ -24,6 +25,9 @@ from devservices.utils.console import Status
|
|
|
24
25
|
from devservices.utils.dependencies import construct_dependency_graph
|
|
25
26
|
from devservices.utils.dependencies import install_and_verify_dependencies
|
|
26
27
|
from devservices.utils.dependencies import InstalledRemoteDependency
|
|
28
|
+
from devservices.utils.docker import check_all_containers_healthy
|
|
29
|
+
from devservices.utils.docker_compose import DockerComposeCommand
|
|
30
|
+
from devservices.utils.docker_compose import get_container_names_for_project
|
|
27
31
|
from devservices.utils.docker_compose import get_docker_compose_commands_to_run
|
|
28
32
|
from devservices.utils.docker_compose import run_cmd
|
|
29
33
|
from devservices.utils.services import find_matching_service
|
|
@@ -101,12 +105,11 @@ def up(args: Namespace) -> None:
|
|
|
101
105
|
|
|
102
106
|
|
|
103
107
|
def _bring_up_dependency(
|
|
104
|
-
cmd:
|
|
108
|
+
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
|
|
105
109
|
) -> subprocess.CompletedProcess[str]:
|
|
106
|
-
|
|
107
|
-
for dependency in cmd[DOCKER_COMPOSE_COMMAND_LENGTH:-len_options]:
|
|
110
|
+
for dependency in cmd.services:
|
|
108
111
|
status.info(f"Starting {dependency}")
|
|
109
|
-
return run_cmd(cmd, current_env)
|
|
112
|
+
return run_cmd(cmd.full_command, current_env)
|
|
110
113
|
|
|
111
114
|
|
|
112
115
|
def _up(
|
|
@@ -128,7 +131,6 @@ def _up(
|
|
|
128
131
|
current_env[
|
|
129
132
|
DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
130
133
|
] = relative_local_dependency_directory
|
|
131
|
-
options = ["-d"]
|
|
132
134
|
dependency_graph = construct_dependency_graph(service, modes=modes)
|
|
133
135
|
starting_order = dependency_graph.get_starting_order()
|
|
134
136
|
sorted_remote_dependencies = sorted(
|
|
@@ -139,13 +141,36 @@ def _up(
|
|
|
139
141
|
remote_dependencies=sorted_remote_dependencies,
|
|
140
142
|
current_env=current_env,
|
|
141
143
|
command="up",
|
|
142
|
-
options=
|
|
144
|
+
options=["-d", "--pull", "always"],
|
|
143
145
|
service_config_file_path=service_config_file_path,
|
|
144
146
|
mode_dependencies=mode_dependencies,
|
|
145
147
|
)
|
|
146
148
|
|
|
149
|
+
containers_to_check = []
|
|
150
|
+
with concurrent.futures.ThreadPoolExecutor() as dependency_executor:
|
|
151
|
+
futures = [
|
|
152
|
+
dependency_executor.submit(_bring_up_dependency, cmd, current_env, status)
|
|
153
|
+
for cmd in docker_compose_commands
|
|
154
|
+
]
|
|
155
|
+
for future in concurrent.futures.as_completed(futures):
|
|
156
|
+
_ = future.result()
|
|
157
|
+
|
|
147
158
|
for cmd in docker_compose_commands:
|
|
148
|
-
|
|
159
|
+
try:
|
|
160
|
+
container_names = get_container_names_for_project(
|
|
161
|
+
cmd.project_name, cmd.config_path
|
|
162
|
+
)
|
|
163
|
+
containers_to_check.extend(container_names)
|
|
164
|
+
except DockerComposeError as dce:
|
|
165
|
+
status.failure(
|
|
166
|
+
f"Failed to get containers to healthcheck for {cmd.project_name}: {dce.stderr}"
|
|
167
|
+
)
|
|
168
|
+
exit(1)
|
|
169
|
+
try:
|
|
170
|
+
check_all_containers_healthy(status, containers_to_check)
|
|
171
|
+
except ContainerHealthcheckFailedError as e:
|
|
172
|
+
status.failure(str(e))
|
|
173
|
+
exit(1)
|
|
149
174
|
|
|
150
175
|
|
|
151
176
|
def _create_devservices_network() -> None:
|
|
@@ -153,4 +178,5 @@ def _create_devservices_network() -> None:
|
|
|
153
178
|
["docker", "network", "create", "devservices"],
|
|
154
179
|
stdout=subprocess.DEVNULL,
|
|
155
180
|
stderr=subprocess.DEVNULL,
|
|
181
|
+
check=True,
|
|
156
182
|
)
|
|
@@ -68,7 +68,7 @@ def load_service_config_from_file(repo_path: str) -> ServiceConfig:
|
|
|
68
68
|
config_path = os.path.join(repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME)
|
|
69
69
|
if not os.path.exists(config_path):
|
|
70
70
|
raise ConfigNotFoundError(f"Config file not found in directory: {config_path}")
|
|
71
|
-
with open(config_path, "r") as stream:
|
|
71
|
+
with open(config_path, "r", encoding="utf-8") as stream:
|
|
72
72
|
try:
|
|
73
73
|
config = yaml.safe_load(stream)
|
|
74
74
|
except yaml.YAMLError as yml_error:
|
|
@@ -14,7 +14,6 @@ DEVSERVICES_DEPENDENCIES_CACHE_DIR = os.path.join(DEVSERVICES_CACHE_DIR, "depend
|
|
|
14
14
|
DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY = "DEVSERVICES_DEPENDENCIES_CACHE_DIR"
|
|
15
15
|
STATE_DB_FILE = os.path.join(DEVSERVICES_LOCAL_DIR, "state")
|
|
16
16
|
DEVSERVICES_ORCHESTRATOR_LABEL = "orchestrator=devservices"
|
|
17
|
-
DOCKER_COMPOSE_COMMAND_LENGTH = 7
|
|
18
17
|
|
|
19
18
|
DEPENDENCY_CONFIG_VERSION = "v1"
|
|
20
19
|
DEPENDENCY_GIT_PARTIAL_CLONE_CONFIG_OPTIONS = {
|
|
@@ -38,3 +37,5 @@ DEVSERVICES_LATEST_VERSION_CACHE_FILE = os.path.join(
|
|
|
38
37
|
DEVSERVICES_CACHE_DIR, "latest_version.txt"
|
|
39
38
|
)
|
|
40
39
|
DEVSERVICES_LATEST_VERSION_CACHE_TTL = timedelta(minutes=15)
|
|
40
|
+
HEALTHCHECK_TIMEOUT = 45
|
|
41
|
+
HEALTHCHECK_INTERVAL = 5
|
|
@@ -127,3 +127,14 @@ class FailedToSetGitConfigError(GitConfigError):
|
|
|
127
127
|
"""Raised when a git config cannot be set."""
|
|
128
128
|
|
|
129
129
|
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ContainerHealthcheckFailedError(Exception):
|
|
133
|
+
"""Raised when a container is not healthy."""
|
|
134
|
+
|
|
135
|
+
def __init__(self, container_name: str, timeout: int):
|
|
136
|
+
self.container_name = container_name
|
|
137
|
+
self.timeout = timeout
|
|
138
|
+
|
|
139
|
+
def __str__(self) -> str:
|
|
140
|
+
return f"Container {self.container_name} did not become healthy within {self.timeout} seconds."
|
|
@@ -26,7 +26,6 @@ from devservices.commands import update
|
|
|
26
26
|
from devservices.constants import LOGGER_NAME
|
|
27
27
|
from devservices.exceptions import DockerComposeInstallationError
|
|
28
28
|
from devservices.exceptions import DockerDaemonNotRunningError
|
|
29
|
-
from devservices.utils.check_for_update import check_for_update
|
|
30
29
|
from devservices.utils.console import Console
|
|
31
30
|
from devservices.utils.docker_compose import check_docker_compose_version
|
|
32
31
|
|
|
@@ -102,14 +101,6 @@ def main() -> None:
|
|
|
102
101
|
else:
|
|
103
102
|
parser.print_help()
|
|
104
103
|
|
|
105
|
-
if args.command != "update" and os.environ.get("CI") != "true":
|
|
106
|
-
newest_version = check_for_update()
|
|
107
|
-
if newest_version != current_version:
|
|
108
|
-
console.warning(
|
|
109
|
-
f"WARNING: A new version of devservices is available: {newest_version}"
|
|
110
|
-
)
|
|
111
|
-
console.warning('To update, run: "devservices update"')
|
|
112
|
-
|
|
113
104
|
|
|
114
105
|
if __name__ == "__main__":
|
|
115
106
|
main()
|
|
@@ -12,6 +12,8 @@ from dataclasses import dataclass
|
|
|
12
12
|
from typing import TextIO
|
|
13
13
|
from typing import TypeGuard
|
|
14
14
|
|
|
15
|
+
from sentry_sdk import set_context
|
|
16
|
+
|
|
15
17
|
from devservices.configs.service_config import Dependency
|
|
16
18
|
from devservices.configs.service_config import load_service_config_from_file
|
|
17
19
|
from devservices.configs.service_config import RemoteConfig
|
|
@@ -36,6 +38,17 @@ from devservices.utils.services import find_matching_service
|
|
|
36
38
|
from devservices.utils.services import Service
|
|
37
39
|
from devservices.utils.state import State
|
|
38
40
|
|
|
41
|
+
RELEVANT_GIT_CONFIG_KEYS = [
|
|
42
|
+
"init.defaultbranch",
|
|
43
|
+
"core.sparsecheckout",
|
|
44
|
+
"remote.origin.url",
|
|
45
|
+
"remote.origin.fetch",
|
|
46
|
+
"remote.origin.promisor",
|
|
47
|
+
"remote.origin.partialclonefilter",
|
|
48
|
+
"protocol.version",
|
|
49
|
+
"extensions.partialclone",
|
|
50
|
+
]
|
|
51
|
+
|
|
39
52
|
|
|
40
53
|
class DependencyGraph:
|
|
41
54
|
def __init__(self) -> None:
|
|
@@ -149,6 +162,28 @@ class GitConfigManager:
|
|
|
149
162
|
if self.sparse_pattern:
|
|
150
163
|
self.sparse_checkout_manager.set_sparse_checkout(self.sparse_pattern)
|
|
151
164
|
|
|
165
|
+
def get_relevant_config(self) -> dict[str, str]:
|
|
166
|
+
"""
|
|
167
|
+
Get the relevant git config entries (to avoid logging sensitive information)
|
|
168
|
+
"""
|
|
169
|
+
git_config = (
|
|
170
|
+
subprocess.check_output(
|
|
171
|
+
["git", "config", "--list"],
|
|
172
|
+
cwd=self.repo_dir,
|
|
173
|
+
stderr=subprocess.PIPE,
|
|
174
|
+
)
|
|
175
|
+
.decode()
|
|
176
|
+
.strip()
|
|
177
|
+
)
|
|
178
|
+
git_config_dict = dict()
|
|
179
|
+
for line in git_config.split("\n"):
|
|
180
|
+
if not line:
|
|
181
|
+
continue
|
|
182
|
+
key, value = line.split("=")
|
|
183
|
+
if key in RELEVANT_GIT_CONFIG_KEYS:
|
|
184
|
+
git_config_dict[key] = value
|
|
185
|
+
return git_config_dict
|
|
186
|
+
|
|
152
187
|
def _set_config(self, key: str, value: str) -> None:
|
|
153
188
|
"""
|
|
154
189
|
Set a git config option for the repo
|
|
@@ -411,12 +446,15 @@ def _update_dependency(
|
|
|
411
446
|
repo_link=dependency.repo_link,
|
|
412
447
|
branch=dependency.branch,
|
|
413
448
|
) from e
|
|
449
|
+
|
|
414
450
|
try:
|
|
415
451
|
_run_command(
|
|
416
452
|
["git", "fetch", "origin", dependency.branch, "--filter=blob:none"],
|
|
417
453
|
cwd=dependency_repo_dir,
|
|
418
454
|
)
|
|
419
455
|
except subprocess.CalledProcessError as e:
|
|
456
|
+
# Try to set the git config context to help with debugging
|
|
457
|
+
_try_set_git_config_context(git_config_manager)
|
|
420
458
|
raise DependencyError(
|
|
421
459
|
repo_name=dependency.repo_name,
|
|
422
460
|
repo_link=dependency.repo_link,
|
|
@@ -536,6 +574,17 @@ def _run_command(
|
|
|
536
574
|
subprocess.run(cmd, cwd=cwd, check=True, stdout=stdout, stderr=subprocess.DEVNULL)
|
|
537
575
|
|
|
538
576
|
|
|
577
|
+
def _try_set_git_config_context(
|
|
578
|
+
git_config_manager: GitConfigManager,
|
|
579
|
+
) -> None:
|
|
580
|
+
try:
|
|
581
|
+
git_config = git_config_manager.get_relevant_config()
|
|
582
|
+
set_context("git_config", git_config)
|
|
583
|
+
except subprocess.CalledProcessError as e:
|
|
584
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
585
|
+
logger.exception(e)
|
|
586
|
+
|
|
587
|
+
|
|
539
588
|
def get_remote_dependency_config(remote_config: RemoteConfig) -> ServiceConfig:
|
|
540
589
|
dependency_repo_dir = os.path.join(
|
|
541
590
|
DEVSERVICES_DEPENDENCIES_CACHE_DIR,
|