devservices 1.0.7__tar.gz → 1.0.9__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.7 → devservices-1.0.9}/PKG-INFO +4 -2
- {devservices-1.0.7 → devservices-1.0.9}/README.md +1 -1
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/down.py +41 -8
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/list_dependencies.py +7 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/logs.py +7 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/status.py +25 -8
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/up.py +9 -3
- {devservices-1.0.7 → devservices-1.0.9}/devservices/configs/service_config.py +4 -2
- {devservices-1.0.7 → devservices-1.0.9}/devservices/constants.py +1 -1
- {devservices-1.0.7 → devservices-1.0.9}/devservices/main.py +0 -9
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/console.py +3 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/dependencies.py +40 -7
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/docker.py +1 -1
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/docker_compose.py +28 -18
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/file_lock.py +1 -1
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/services.py +8 -1
- {devservices-1.0.7 → devservices-1.0.9}/devservices.egg-info/PKG-INFO +4 -2
- {devservices-1.0.7 → devservices-1.0.9}/devservices.egg-info/requires.txt +2 -0
- {devservices-1.0.7 → devservices-1.0.9}/pyproject.toml +3 -1
- devservices-1.0.9/tests/commands/test_down.py +861 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_list_dependencies.py +21 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_logs.py +22 -1
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_status.py +96 -1
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_up.py +171 -15
- {devservices-1.0.7 → devservices-1.0.9}/tests/configs/test_service_config.py +1 -1
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_docker_compose.py +3 -9
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_services.py +65 -2
- devservices-1.0.7/tests/commands/test_down.py +0 -265
- {devservices-1.0.7 → devservices-1.0.9}/LICENSE.md +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/__init__.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/__init__.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/list_services.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/purge.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/commands/update.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/exceptions.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/__init__.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/check_for_update.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/devenv.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/install_binary.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices/utils/state.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices.egg-info/SOURCES.txt +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/setup.cfg +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/testing/__init__.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/testing/utils.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/__init__.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_list_services.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_purge.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/commands/test_update.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/conftest.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_check_for_update.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_dependencies.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_docker.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_install_binary.py +0 -0
- {devservices-1.0.7 → devservices-1.0.9}/tests/utils/test_state.py +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: devservices
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.9
|
|
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"
|
|
@@ -15,11 +15,13 @@ 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
17
|
from devservices.exceptions import ConfigError
|
|
18
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
18
19
|
from devservices.exceptions import DependencyError
|
|
19
20
|
from devservices.exceptions import DockerComposeError
|
|
20
21
|
from devservices.exceptions import ServiceNotFoundError
|
|
21
22
|
from devservices.utils.console import Console
|
|
22
23
|
from devservices.utils.console import Status
|
|
24
|
+
from devservices.utils.dependencies import construct_dependency_graph
|
|
23
25
|
from devservices.utils.dependencies import get_non_shared_remote_dependencies
|
|
24
26
|
from devservices.utils.dependencies import install_and_verify_dependencies
|
|
25
27
|
from devservices.utils.dependencies import InstalledRemoteDependency
|
|
@@ -56,6 +58,12 @@ def down(args: Namespace) -> None:
|
|
|
56
58
|
service_name = args.service_name
|
|
57
59
|
try:
|
|
58
60
|
service = find_matching_service(service_name)
|
|
61
|
+
except ConfigNotFoundError as e:
|
|
62
|
+
capture_exception(e)
|
|
63
|
+
console.failure(
|
|
64
|
+
f"{str(e)}. Please specify a service (i.e. `devservices down sentry`) or run the command from a directory with a devservices configuration."
|
|
65
|
+
)
|
|
66
|
+
exit(1)
|
|
59
67
|
except ConfigError as e:
|
|
60
68
|
capture_exception(e)
|
|
61
69
|
console.failure(str(e))
|
|
@@ -80,7 +88,6 @@ def down(args: Namespace) -> None:
|
|
|
80
88
|
|
|
81
89
|
with Status(
|
|
82
90
|
lambda: console.warning(f"Stopping {service.name}"),
|
|
83
|
-
lambda: console.success(f"{service.name} stopped"),
|
|
84
91
|
) as status:
|
|
85
92
|
try:
|
|
86
93
|
remote_dependencies = install_and_verify_dependencies(
|
|
@@ -98,16 +105,42 @@ def down(args: Namespace) -> None:
|
|
|
98
105
|
capture_exception(de)
|
|
99
106
|
status.failure(str(de))
|
|
100
107
|
exit(1)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
|
|
109
|
+
# Check if any service depends on the service we are trying to bring down
|
|
110
|
+
# TODO: We should also take into account the active modes of the other services (this is not trivial to do)
|
|
111
|
+
other_started_services = set(started_services).difference({service.name})
|
|
112
|
+
dependent_service_name = None
|
|
113
|
+
for other_started_service in other_started_services:
|
|
114
|
+
other_service = find_matching_service(other_started_service)
|
|
115
|
+
other_service_active_modes = state.get_active_modes_for_service(
|
|
116
|
+
other_service.name
|
|
117
|
+
)
|
|
118
|
+
dependency_graph = construct_dependency_graph(
|
|
119
|
+
other_service, other_service_active_modes
|
|
120
|
+
)
|
|
121
|
+
# If the service we are trying to bring down is in the dependency graph of another service, we should not bring it down
|
|
122
|
+
if service.name in dependency_graph.graph:
|
|
123
|
+
dependent_service_name = other_started_service
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
# If no other service depends on the service we are trying to bring down, we can bring it down
|
|
127
|
+
if dependent_service_name is None:
|
|
128
|
+
try:
|
|
129
|
+
_down(service, remote_dependencies, list(mode_dependencies), status)
|
|
130
|
+
except DockerComposeError as dce:
|
|
131
|
+
capture_exception(dce)
|
|
132
|
+
status.failure(f"Failed to stop {service.name}: {dce.stderr}")
|
|
133
|
+
exit(1)
|
|
134
|
+
else:
|
|
135
|
+
status.warning(
|
|
136
|
+
f"Leaving {service.name} running because it is being used by {dependent_service_name}"
|
|
137
|
+
)
|
|
107
138
|
|
|
108
139
|
# TODO: We should factor in healthchecks here before marking service as not running
|
|
109
140
|
state = State()
|
|
110
141
|
state.remove_started_service(service.name)
|
|
142
|
+
if dependent_service_name is None:
|
|
143
|
+
console.success(f"{service.name} stopped")
|
|
111
144
|
|
|
112
145
|
|
|
113
146
|
def _bring_down_dependency(
|
|
@@ -141,7 +174,7 @@ def _down(
|
|
|
141
174
|
service=service,
|
|
142
175
|
remote_dependencies=list(remote_dependencies),
|
|
143
176
|
current_env=current_env,
|
|
144
|
-
command="
|
|
177
|
+
command="stop",
|
|
145
178
|
options=[],
|
|
146
179
|
service_config_file_path=service_config_file_path,
|
|
147
180
|
mode_dependencies=mode_dependencies,
|
|
@@ -7,6 +7,7 @@ from argparse import Namespace
|
|
|
7
7
|
from sentry_sdk import capture_exception
|
|
8
8
|
|
|
9
9
|
from devservices.exceptions import ConfigError
|
|
10
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
10
11
|
from devservices.exceptions import ServiceNotFoundError
|
|
11
12
|
from devservices.utils.console import Console
|
|
12
13
|
from devservices.utils.services import find_matching_service
|
|
@@ -32,6 +33,12 @@ def list_dependencies(args: Namespace) -> None:
|
|
|
32
33
|
|
|
33
34
|
try:
|
|
34
35
|
service = find_matching_service(service_name)
|
|
36
|
+
except ConfigNotFoundError as e:
|
|
37
|
+
capture_exception(e)
|
|
38
|
+
console.failure(
|
|
39
|
+
f"{str(e)}. Please specify a service (i.e. `devservices list-dependencies sentry`) or run the command from a directory with a devservices configuration."
|
|
40
|
+
)
|
|
41
|
+
exit(1)
|
|
35
42
|
except ConfigError as e:
|
|
36
43
|
capture_exception(e)
|
|
37
44
|
console.failure(str(e))
|
|
@@ -16,6 +16,7 @@ from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
|
16
16
|
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
17
17
|
from devservices.constants import MAX_LOG_LINES
|
|
18
18
|
from devservices.exceptions import ConfigError
|
|
19
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
19
20
|
from devservices.exceptions import DependencyError
|
|
20
21
|
from devservices.exceptions import DockerComposeError
|
|
21
22
|
from devservices.exceptions import ServiceNotFoundError
|
|
@@ -46,6 +47,12 @@ def logs(args: Namespace) -> None:
|
|
|
46
47
|
service_name = args.service_name
|
|
47
48
|
try:
|
|
48
49
|
service = find_matching_service(service_name)
|
|
50
|
+
except ConfigNotFoundError as e:
|
|
51
|
+
capture_exception(e)
|
|
52
|
+
console.failure(
|
|
53
|
+
f"{str(e)}. Please specify a service (i.e. `devservices logs sentry`) or run the command from a directory with a devservices configuration."
|
|
54
|
+
)
|
|
55
|
+
exit(1)
|
|
49
56
|
except ConfigError as e:
|
|
50
57
|
capture_exception(e)
|
|
51
58
|
console.failure(str(e))
|
|
@@ -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
|
|
|
@@ -16,6 +17,7 @@ from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR
|
|
|
16
17
|
from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
17
18
|
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
18
19
|
from devservices.exceptions import ConfigError
|
|
20
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
19
21
|
from devservices.exceptions import DependencyError
|
|
20
22
|
from devservices.exceptions import DockerComposeError
|
|
21
23
|
from devservices.exceptions import ServiceNotFoundError
|
|
@@ -30,6 +32,9 @@ from devservices.utils.services import Service
|
|
|
30
32
|
LINE_LENGTH = 40
|
|
31
33
|
|
|
32
34
|
|
|
35
|
+
ServiceStatus = namedtuple("ServiceStatus", ["name", "formatted_output"])
|
|
36
|
+
|
|
37
|
+
|
|
33
38
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
34
39
|
parser = subparsers.add_parser("status", help="View status of a service")
|
|
35
40
|
parser.add_argument(
|
|
@@ -41,12 +46,12 @@ def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
41
46
|
parser.set_defaults(func=status)
|
|
42
47
|
|
|
43
48
|
|
|
44
|
-
def format_status_output(
|
|
49
|
+
def format_status_output(service_status_json: str) -> list[ServiceStatus]:
|
|
45
50
|
# 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)
|
|
51
|
+
service_statuses = service_status_json.split("\n")[:-1]
|
|
52
|
+
outputs = []
|
|
49
53
|
for service_status in service_statuses:
|
|
54
|
+
output = []
|
|
50
55
|
service = json.loads(service_status)
|
|
51
56
|
name = service["Service"]
|
|
52
57
|
state = service["State"]
|
|
@@ -71,8 +76,9 @@ def format_status_output(status_json: str) -> str:
|
|
|
71
76
|
output.append("No ports exposed")
|
|
72
77
|
|
|
73
78
|
output.append("") # Empty line for readability
|
|
79
|
+
outputs.append(ServiceStatus(name=name, formatted_output="\n".join(output)))
|
|
74
80
|
|
|
75
|
-
return
|
|
81
|
+
return outputs
|
|
76
82
|
|
|
77
83
|
|
|
78
84
|
def status(args: Namespace) -> None:
|
|
@@ -81,6 +87,12 @@ def status(args: Namespace) -> None:
|
|
|
81
87
|
service_name = args.service_name
|
|
82
88
|
try:
|
|
83
89
|
service = find_matching_service(service_name)
|
|
90
|
+
except ConfigNotFoundError as e:
|
|
91
|
+
capture_exception(e)
|
|
92
|
+
console.failure(
|
|
93
|
+
f"{str(e)}. Please specify a service (i.e. `devservices status sentry`) or run the command from a directory with a devservices configuration."
|
|
94
|
+
)
|
|
95
|
+
exit(1)
|
|
84
96
|
except ConfigError as e:
|
|
85
97
|
capture_exception(e)
|
|
86
98
|
console.failure(str(e))
|
|
@@ -115,10 +127,15 @@ def status(args: Namespace) -> None:
|
|
|
115
127
|
console.warning(f"{service.name} is not running")
|
|
116
128
|
return
|
|
117
129
|
output = f"Service: {service.name}\n\n"
|
|
130
|
+
output += "=" * LINE_LENGTH + "\n"
|
|
131
|
+
formatted_status_outputs = []
|
|
118
132
|
for status_json in status_json_results:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
formatted_status_outputs.extend(format_status_output(status_json.stdout))
|
|
134
|
+
formatted_status_outputs.sort(key=lambda x: x.name)
|
|
135
|
+
for formatted_status_output in formatted_status_outputs:
|
|
136
|
+
output += formatted_status_output[1]
|
|
137
|
+
output += "-" * LINE_LENGTH + "\n"
|
|
138
|
+
console.info(output)
|
|
122
139
|
|
|
123
140
|
|
|
124
141
|
def _status(
|
|
@@ -15,6 +15,7 @@ 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
17
|
from devservices.exceptions import ConfigError
|
|
18
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
18
19
|
from devservices.exceptions import ContainerHealthcheckFailedError
|
|
19
20
|
from devservices.exceptions import DependencyError
|
|
20
21
|
from devservices.exceptions import DockerComposeError
|
|
@@ -60,6 +61,12 @@ def up(args: Namespace) -> None:
|
|
|
60
61
|
service_name = args.service_name
|
|
61
62
|
try:
|
|
62
63
|
service = find_matching_service(service_name)
|
|
64
|
+
except ConfigNotFoundError as e:
|
|
65
|
+
capture_exception(e)
|
|
66
|
+
console.failure(
|
|
67
|
+
f"{str(e)}. Please specify a service (i.e. `devservices up sentry`) or run the command from a directory with a devservices configuration."
|
|
68
|
+
)
|
|
69
|
+
exit(1)
|
|
63
70
|
except ConfigError as e:
|
|
64
71
|
capture_exception(e)
|
|
65
72
|
console.failure(str(e))
|
|
@@ -107,7 +114,6 @@ def up(args: Namespace) -> None:
|
|
|
107
114
|
def _bring_up_dependency(
|
|
108
115
|
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
|
|
109
116
|
) -> subprocess.CompletedProcess[str]:
|
|
110
|
-
# TODO: Get rid of these constants, we need a smarter way to determine the containers being brought up
|
|
111
117
|
for dependency in cmd.services:
|
|
112
118
|
status.info(f"Starting {dependency}")
|
|
113
119
|
return run_cmd(cmd.full_command, current_env)
|
|
@@ -132,7 +138,6 @@ def _up(
|
|
|
132
138
|
current_env[
|
|
133
139
|
DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
|
|
134
140
|
] = relative_local_dependency_directory
|
|
135
|
-
options = ["-d", "--pull", "always"]
|
|
136
141
|
dependency_graph = construct_dependency_graph(service, modes=modes)
|
|
137
142
|
starting_order = dependency_graph.get_starting_order()
|
|
138
143
|
sorted_remote_dependencies = sorted(
|
|
@@ -143,7 +148,7 @@ def _up(
|
|
|
143
148
|
remote_dependencies=sorted_remote_dependencies,
|
|
144
149
|
current_env=current_env,
|
|
145
150
|
command="up",
|
|
146
|
-
options=
|
|
151
|
+
options=["-d", "--pull", "always"],
|
|
147
152
|
service_config_file_path=service_config_file_path,
|
|
148
153
|
mode_dependencies=mode_dependencies,
|
|
149
154
|
)
|
|
@@ -180,4 +185,5 @@ def _create_devservices_network() -> None:
|
|
|
180
185
|
["docker", "network", "create", "devservices"],
|
|
181
186
|
stdout=subprocess.DEVNULL,
|
|
182
187
|
stderr=subprocess.DEVNULL,
|
|
188
|
+
check=True,
|
|
183
189
|
)
|
|
@@ -67,8 +67,10 @@ class ServiceConfig:
|
|
|
67
67
|
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
|
-
raise ConfigNotFoundError(
|
|
71
|
-
|
|
70
|
+
raise ConfigNotFoundError(
|
|
71
|
+
f"No devservices configuration found in {config_path}"
|
|
72
|
+
)
|
|
73
|
+
with open(config_path, "r", encoding="utf-8") as stream:
|
|
72
74
|
try:
|
|
73
75
|
config = yaml.safe_load(stream)
|
|
74
76
|
except yaml.YAMLError as yml_error:
|
|
@@ -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()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import sys
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
@@ -97,6 +98,8 @@ class Status:
|
|
|
97
98
|
self.on_success()
|
|
98
99
|
|
|
99
100
|
def _loading_animation(self) -> None:
|
|
101
|
+
if os.environ.get("CI", default="false") == "true":
|
|
102
|
+
return
|
|
100
103
|
idx = 0
|
|
101
104
|
while not self._stop_loading.is_set():
|
|
102
105
|
sys.stdout.write("\r" + ANIMATION_FRAMES[idx % len(ANIMATION_FRAMES)] + " ")
|
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import shutil
|
|
6
6
|
import subprocess
|
|
7
7
|
import tempfile
|
|
8
|
+
import time
|
|
8
9
|
from collections import deque
|
|
9
10
|
from concurrent.futures import as_completed
|
|
10
11
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -264,15 +265,28 @@ def get_non_shared_remote_dependencies(
|
|
|
264
265
|
# We don't care about the remote dependencies of the service we are stopping
|
|
265
266
|
started_services.remove(service_to_stop.name)
|
|
266
267
|
other_running_remote_dependencies: set[InstalledRemoteDependency] = set()
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
base_running_service_names: set[str] = set()
|
|
269
|
+
for started_service_name in started_services:
|
|
270
|
+
started_service = find_matching_service(started_service_name)
|
|
271
|
+
for dependency_name in service_to_stop.config.dependencies.keys():
|
|
272
|
+
if dependency_name == started_service.config.service_name:
|
|
273
|
+
base_running_service_names.add(started_service_name)
|
|
274
|
+
installed_remote_dependencies = get_installed_remote_dependencies(
|
|
275
|
+
list(started_service.config.dependencies.values())
|
|
276
|
+
)
|
|
269
277
|
# TODO: There is an edge case here where there is a shared remote dependency with different modes
|
|
270
278
|
other_running_remote_dependencies = other_running_remote_dependencies.union(
|
|
271
|
-
|
|
272
|
-
list(service.config.dependencies.values())
|
|
273
|
-
)
|
|
279
|
+
installed_remote_dependencies
|
|
274
280
|
)
|
|
275
|
-
|
|
281
|
+
non_shared_remote_dependencies = remote_dependencies.difference(
|
|
282
|
+
other_running_remote_dependencies
|
|
283
|
+
)
|
|
284
|
+
non_shared_remote_dependencies = {
|
|
285
|
+
dependency
|
|
286
|
+
for dependency in non_shared_remote_dependencies
|
|
287
|
+
if dependency.service_name not in base_running_service_names
|
|
288
|
+
}
|
|
289
|
+
return non_shared_remote_dependencies
|
|
276
290
|
|
|
277
291
|
|
|
278
292
|
def get_installed_remote_dependencies(
|
|
@@ -448,7 +462,7 @@ def _update_dependency(
|
|
|
448
462
|
) from e
|
|
449
463
|
|
|
450
464
|
try:
|
|
451
|
-
|
|
465
|
+
_run_command_with_retries(
|
|
452
466
|
["git", "fetch", "origin", dependency.branch, "--filter=blob:none"],
|
|
453
467
|
cwd=dependency_repo_dir,
|
|
454
468
|
)
|
|
@@ -574,6 +588,25 @@ def _run_command(
|
|
|
574
588
|
subprocess.run(cmd, cwd=cwd, check=True, stdout=stdout, stderr=subprocess.DEVNULL)
|
|
575
589
|
|
|
576
590
|
|
|
591
|
+
def _run_command_with_retries(
|
|
592
|
+
cmd: list[str],
|
|
593
|
+
cwd: str,
|
|
594
|
+
stdout: int | TextIO | None = subprocess.DEVNULL,
|
|
595
|
+
retries: int = 3,
|
|
596
|
+
backoff: int = 2,
|
|
597
|
+
) -> None:
|
|
598
|
+
for i in range(retries):
|
|
599
|
+
try:
|
|
600
|
+
_run_command(cmd, cwd=cwd, stdout=stdout)
|
|
601
|
+
break
|
|
602
|
+
except subprocess.CalledProcessError as e:
|
|
603
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
604
|
+
logger.exception("Attempt %s of %s failed: %s", i + 1, retries, e)
|
|
605
|
+
if i == retries - 1:
|
|
606
|
+
raise e
|
|
607
|
+
time.sleep(backoff**i)
|
|
608
|
+
|
|
609
|
+
|
|
577
610
|
def _try_set_git_config_context(
|
|
578
611
|
git_config_manager: GitConfigManager,
|
|
579
612
|
) -> None:
|
|
@@ -5,7 +5,6 @@ import os
|
|
|
5
5
|
import platform
|
|
6
6
|
import re
|
|
7
7
|
import subprocess
|
|
8
|
-
from collections.abc import Callable
|
|
9
8
|
from typing import cast
|
|
10
9
|
from typing import NamedTuple
|
|
11
10
|
|
|
@@ -170,7 +169,7 @@ def check_docker_compose_version() -> None:
|
|
|
170
169
|
|
|
171
170
|
|
|
172
171
|
# TODO: Consider removing this in favor of in house logic for determining non-remote services
|
|
173
|
-
def
|
|
172
|
+
def get_non_remote_services(
|
|
174
173
|
service_config_path: str, current_env: dict[str, str]
|
|
175
174
|
) -> set[str]:
|
|
176
175
|
config_command = [
|
|
@@ -195,19 +194,14 @@ def _get_non_remote_services(
|
|
|
195
194
|
return set(config_services.splitlines())
|
|
196
195
|
|
|
197
196
|
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
def create_docker_compose_command(
|
|
198
|
+
name: str,
|
|
199
|
+
config_path: str,
|
|
200
|
+
services_to_use: set[str],
|
|
202
201
|
command: str,
|
|
203
202
|
options: list[str],
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
) -> list[DockerComposeCommand]:
|
|
207
|
-
docker_compose_commands = []
|
|
208
|
-
create_docker_compose_command: Callable[
|
|
209
|
-
[str, str, set[str]], DockerComposeCommand
|
|
210
|
-
] = lambda name, config_path, services_to_use: DockerComposeCommand(
|
|
203
|
+
) -> DockerComposeCommand:
|
|
204
|
+
return DockerComposeCommand(
|
|
211
205
|
full_command=[
|
|
212
206
|
"docker",
|
|
213
207
|
"compose",
|
|
@@ -223,13 +217,25 @@ def get_docker_compose_commands_to_run(
|
|
|
223
217
|
config_path=config_path,
|
|
224
218
|
services=sorted(list(services_to_use)),
|
|
225
219
|
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_docker_compose_commands_to_run(
|
|
223
|
+
service: Service,
|
|
224
|
+
remote_dependencies: list[InstalledRemoteDependency],
|
|
225
|
+
current_env: dict[str, str],
|
|
226
|
+
command: str,
|
|
227
|
+
options: list[str],
|
|
228
|
+
service_config_file_path: str,
|
|
229
|
+
mode_dependencies: list[str],
|
|
230
|
+
) -> list[DockerComposeCommand]:
|
|
231
|
+
docker_compose_commands = []
|
|
226
232
|
for dependency in remote_dependencies:
|
|
227
233
|
# TODO: Consider passing in service config in InstalledRemoteDependency instead of loading it here
|
|
228
234
|
dependency_service_config = load_service_config_from_file(dependency.repo_path)
|
|
229
235
|
dependency_config_path = os.path.join(
|
|
230
236
|
dependency.repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME
|
|
231
237
|
)
|
|
232
|
-
non_remote_services =
|
|
238
|
+
non_remote_services = get_non_remote_services(
|
|
233
239
|
dependency_config_path, current_env
|
|
234
240
|
)
|
|
235
241
|
services_to_use = non_remote_services.intersection(
|
|
@@ -240,18 +246,22 @@ def get_docker_compose_commands_to_run(
|
|
|
240
246
|
dependency_service_config.service_name,
|
|
241
247
|
dependency_config_path,
|
|
242
248
|
services_to_use,
|
|
249
|
+
command,
|
|
250
|
+
options,
|
|
243
251
|
)
|
|
244
252
|
)
|
|
245
253
|
|
|
246
254
|
# Add docker compose command for the top level service
|
|
247
|
-
non_remote_services =
|
|
248
|
-
service_config_file_path, current_env
|
|
249
|
-
)
|
|
255
|
+
non_remote_services = get_non_remote_services(service_config_file_path, current_env)
|
|
250
256
|
services_to_use = non_remote_services.intersection(set(mode_dependencies))
|
|
251
257
|
if len(services_to_use) > 0:
|
|
252
258
|
docker_compose_commands.append(
|
|
253
259
|
create_docker_compose_command(
|
|
254
|
-
service.name,
|
|
260
|
+
service.name,
|
|
261
|
+
service_config_file_path,
|
|
262
|
+
services_to_use,
|
|
263
|
+
command,
|
|
264
|
+
options,
|
|
255
265
|
)
|
|
256
266
|
)
|
|
257
267
|
return docker_compose_commands
|
|
@@ -65,4 +65,11 @@ def find_matching_service(service_name: str | None = None) -> Service:
|
|
|
65
65
|
for service in services:
|
|
66
66
|
if service.name.lower() == service_name.lower():
|
|
67
67
|
return service
|
|
68
|
-
|
|
68
|
+
unique_service_names = sorted(set(service.name for service in services))
|
|
69
|
+
error_message = f"Service '{service_name}' not found."
|
|
70
|
+
if len(unique_service_names) > 0:
|
|
71
|
+
service_bullet_points = "\n".join(
|
|
72
|
+
[f"- {service_name}" for service_name in unique_service_names]
|
|
73
|
+
)
|
|
74
|
+
error_message += "\nSupported services:\n" + service_bullet_points
|
|
75
|
+
raise ServiceNotFoundError(error_message)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: devservices
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.9
|
|
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"
|
|
@@ -4,12 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devservices"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.9"
|
|
8
8
|
# 3.10 is just for internal pypi compat
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pyyaml",
|
|
12
12
|
"sentry-devenv",
|
|
13
|
+
"sentry-sdk",
|
|
14
|
+
"packaging",
|
|
13
15
|
]
|
|
14
16
|
|
|
15
17
|
[project.optional-dependencies]
|