devservices 0.0.1__tar.gz → 0.0.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-0.0.1 → devservices-0.0.3}/PKG-INFO +2 -2
- devservices-0.0.3/README.md +22 -0
- devservices-0.0.3/devservices/__init__.py +4 -0
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/list_dependencies.py +1 -1
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/list_services.py +2 -2
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/logs.py +6 -6
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/start.py +7 -7
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/status.py +8 -9
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/stop.py +7 -7
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/configs/service_config.py +14 -9
- devservices-0.0.3/devservices/constants.py +4 -0
- devservices-0.0.3/devservices/exceptions.py +41 -0
- devservices-0.0.3/devservices/main.py +62 -0
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/utils/docker_compose.py +1 -1
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/utils/services.py +8 -8
- {devservices-0.0.1/src → devservices-0.0.3}/devservices.egg-info/PKG-INFO +2 -2
- devservices-0.0.3/devservices.egg-info/SOURCES.txt +32 -0
- devservices-0.0.3/devservices.egg-info/entry_points.txt +2 -0
- devservices-0.0.3/devservices.egg-info/top_level.txt +4 -0
- {devservices-0.0.1 → devservices-0.0.3}/pyproject.toml +8 -8
- devservices-0.0.3/tests/__init__.py +0 -0
- devservices-0.0.3/tests/commands/test_start.py +97 -0
- devservices-0.0.3/tests/commands/test_stop.py +97 -0
- devservices-0.0.3/tests/configs/test_service_config.py +334 -0
- devservices-0.0.3/tests/conftest.py +0 -0
- {devservices-0.0.1 → devservices-0.0.3}/tests/testutils.py +1 -1
- devservices-0.0.1/README.md +0 -1
- devservices-0.0.1/src/devservices.egg-info/SOURCES.txt +0 -23
- devservices-0.0.1/src/devservices.egg-info/entry_points.txt +0 -2
- devservices-0.0.1/src/devservices.egg-info/top_level.txt +0 -3
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/commands/__init__.py +0 -0
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/utils/__init__.py +0 -0
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/utils/console.py +0 -0
- {devservices-0.0.1/src → devservices-0.0.3/devservices}/utils/devenv.py +0 -0
- {devservices-0.0.1/src → devservices-0.0.3}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-0.0.1/src → devservices-0.0.3}/devservices.egg-info/requires.txt +0 -0
- {devservices-0.0.1 → devservices-0.0.3}/setup.cfg +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# devservices
|
|
2
|
+
|
|
3
|
+
A standalone cli tool used to manage dependencies for services. It simplifies the process of starting, stopping, and managing services for development purposes.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`devservices` reads configuration files located in the `devservices` directory of your repository. These configurations define services, their dependencies, and various modes of operation.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
devservices provides several commands to manage your services:
|
|
12
|
+
|
|
13
|
+
### Commands
|
|
14
|
+
|
|
15
|
+
NOTE: service-name is an optional parameter. If not provided, devservices will attempt to automatically find a devservices configuration in the current directory in order to proceed.
|
|
16
|
+
|
|
17
|
+
- `devservices start <service-name>`: Start a service and its dependencies.
|
|
18
|
+
- `devservices stop <service-name>`: Stop a service including its dependencies.
|
|
19
|
+
- `devservices status <service-name>`: Display the current status of all services, including their dependencies and ports.
|
|
20
|
+
- `devservices logs <service-name>`: View logs for a specific service.
|
|
21
|
+
- `devservices list-services`: List all available Sentry services.
|
|
22
|
+
- `devservices list-dependencies <service-name>`: List all dependencies for a service and whether they are enabled/disabled.
|
|
@@ -4,7 +4,7 @@ from argparse import _SubParsersAction
|
|
|
4
4
|
from argparse import ArgumentParser
|
|
5
5
|
from argparse import Namespace
|
|
6
6
|
|
|
7
|
-
from utils.services import find_matching_service
|
|
7
|
+
from devservices.utils.services import find_matching_service
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -4,8 +4,8 @@ from argparse import _SubParsersAction
|
|
|
4
4
|
from argparse import ArgumentParser
|
|
5
5
|
from argparse import Namespace
|
|
6
6
|
|
|
7
|
-
from utils.devenv import get_coderoot
|
|
8
|
-
from utils.services import get_local_services
|
|
7
|
+
from devservices.utils.devenv import get_coderoot
|
|
8
|
+
from devservices.utils.services import get_local_services
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -6,11 +6,11 @@ from argparse import _SubParsersAction
|
|
|
6
6
|
from argparse import ArgumentParser
|
|
7
7
|
from argparse import Namespace
|
|
8
8
|
|
|
9
|
-
from constants import
|
|
10
|
-
from constants import
|
|
11
|
-
from exceptions import DockerComposeError
|
|
12
|
-
from utils.docker_compose import run_docker_compose_command
|
|
13
|
-
from utils.services import find_matching_service
|
|
9
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
10
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
11
|
+
from devservices.exceptions import DockerComposeError
|
|
12
|
+
from devservices.utils.docker_compose import run_docker_compose_command
|
|
13
|
+
from devservices.utils.services import find_matching_service
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -37,7 +37,7 @@ def logs(args: Namespace) -> None:
|
|
|
37
37
|
mode_to_use = "default"
|
|
38
38
|
mode_dependencies = " ".join(modes[mode_to_use])
|
|
39
39
|
service_config_file_path = os.path.join(
|
|
40
|
-
service.repo_path, DEVSERVICES_DIR_NAME,
|
|
40
|
+
service.repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME
|
|
41
41
|
)
|
|
42
42
|
try:
|
|
43
43
|
logs = run_docker_compose_command(
|
|
@@ -5,12 +5,12 @@ from argparse import _SubParsersAction
|
|
|
5
5
|
from argparse import ArgumentParser
|
|
6
6
|
from argparse import Namespace
|
|
7
7
|
|
|
8
|
-
from constants import
|
|
9
|
-
from constants import
|
|
10
|
-
from exceptions import DockerComposeError
|
|
11
|
-
from utils.console import Status
|
|
12
|
-
from utils.docker_compose import run_docker_compose_command
|
|
13
|
-
from utils.services import find_matching_service
|
|
8
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
9
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
10
|
+
from devservices.exceptions import DockerComposeError
|
|
11
|
+
from devservices.utils.console import Status
|
|
12
|
+
from devservices.utils.docker_compose import run_docker_compose_command
|
|
13
|
+
from devservices.utils.services import find_matching_service
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -34,7 +34,7 @@ def start(args: Namespace) -> None:
|
|
|
34
34
|
mode_to_start = "default"
|
|
35
35
|
mode_dependencies = " ".join(modes[mode_to_start])
|
|
36
36
|
service_config_file_path = os.path.join(
|
|
37
|
-
service.repo_path, DEVSERVICES_DIR_NAME,
|
|
37
|
+
service.repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME
|
|
38
38
|
)
|
|
39
39
|
with Status(f"Starting {service.name}", f"{service.name} started") as status:
|
|
40
40
|
try:
|
|
@@ -7,11 +7,11 @@ from argparse import _SubParsersAction
|
|
|
7
7
|
from argparse import ArgumentParser
|
|
8
8
|
from argparse import Namespace
|
|
9
9
|
|
|
10
|
-
from constants import
|
|
11
|
-
from constants import
|
|
12
|
-
from exceptions import DockerComposeError
|
|
13
|
-
from utils.docker_compose import run_docker_compose_command
|
|
14
|
-
from utils.services import find_matching_service
|
|
10
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
11
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
12
|
+
from devservices.exceptions import DockerComposeError
|
|
13
|
+
from devservices.utils.docker_compose import run_docker_compose_command
|
|
14
|
+
from devservices.utils.services import find_matching_service
|
|
15
15
|
|
|
16
16
|
LINE_LENGTH = 40
|
|
17
17
|
|
|
@@ -49,7 +49,7 @@ def format_status_output(status_json: str) -> str:
|
|
|
49
49
|
output.append("Ports:")
|
|
50
50
|
for port in ports:
|
|
51
51
|
output.append(
|
|
52
|
-
f" {port['PublishedPort']} -> {port['TargetPort']}/{port['Protocol']}"
|
|
52
|
+
f" {port['URL']}:{port['PublishedPort']} -> {port['TargetPort']}/{port['Protocol']}"
|
|
53
53
|
)
|
|
54
54
|
else:
|
|
55
55
|
output.append("No ports exposed")
|
|
@@ -70,11 +70,10 @@ def status(args: Namespace) -> None:
|
|
|
70
70
|
modes = service.config.modes
|
|
71
71
|
# TODO: allow custom modes to be used
|
|
72
72
|
mode_to_view = "default"
|
|
73
|
-
mode_dependencies = modes[mode_to_view]
|
|
73
|
+
mode_dependencies = " ".join(modes[mode_to_view])
|
|
74
74
|
service_config_file_path = os.path.join(
|
|
75
|
-
service.repo_path, DEVSERVICES_DIR_NAME,
|
|
75
|
+
service.repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME
|
|
76
76
|
)
|
|
77
|
-
mode_dependencies = " ".join(modes[mode_to_view])
|
|
78
77
|
try:
|
|
79
78
|
status_json = run_docker_compose_command(
|
|
80
79
|
f"-f {service_config_file_path} ps {mode_dependencies} --format json"
|
|
@@ -5,12 +5,12 @@ from argparse import _SubParsersAction
|
|
|
5
5
|
from argparse import ArgumentParser
|
|
6
6
|
from argparse import Namespace
|
|
7
7
|
|
|
8
|
-
from constants import
|
|
9
|
-
from constants import
|
|
10
|
-
from exceptions import DockerComposeError
|
|
11
|
-
from utils.console import Status
|
|
12
|
-
from utils.docker_compose import run_docker_compose_command
|
|
13
|
-
from utils.services import find_matching_service
|
|
8
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
9
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
10
|
+
from devservices.exceptions import DockerComposeError
|
|
11
|
+
from devservices.utils.console import Status
|
|
12
|
+
from devservices.utils.docker_compose import run_docker_compose_command
|
|
13
|
+
from devservices.utils.services import find_matching_service
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -34,7 +34,7 @@ def stop(args: Namespace) -> None:
|
|
|
34
34
|
mode_to_stop = "default"
|
|
35
35
|
mode_dependencies = " ".join(modes[mode_to_stop])
|
|
36
36
|
service_config_file_path = os.path.join(
|
|
37
|
-
service.repo_path, DEVSERVICES_DIR_NAME,
|
|
37
|
+
service.repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME
|
|
38
38
|
)
|
|
39
39
|
with Status(f"Stopping {service.name}", f"{service.name} stopped") as status:
|
|
40
40
|
try:
|
|
@@ -4,20 +4,27 @@ import os
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
6
|
import yaml
|
|
7
|
-
from constants import DEVSERVICES_DIR_NAME
|
|
8
|
-
from constants import DOCKER_COMPOSE_FILE_NAME
|
|
9
|
-
from exceptions import ConfigNotFoundError
|
|
10
|
-
from exceptions import ConfigParseError
|
|
11
|
-
from exceptions import ConfigValidationError
|
|
12
7
|
|
|
8
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
9
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
10
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
11
|
+
from devservices.exceptions import ConfigParseError
|
|
12
|
+
from devservices.exceptions import ConfigValidationError
|
|
13
13
|
|
|
14
14
|
VALID_VERSIONS = [0.1]
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
@dataclass
|
|
18
|
+
class RemoteConfig:
|
|
19
|
+
repo_name: str
|
|
20
|
+
branch: str
|
|
21
|
+
repo_link: str
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
@dataclass
|
|
18
25
|
class Dependency:
|
|
19
26
|
description: str
|
|
20
|
-
|
|
27
|
+
remote: RemoteConfig | None = None
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
@dataclass
|
|
@@ -56,9 +63,7 @@ class ServiceConfig:
|
|
|
56
63
|
|
|
57
64
|
|
|
58
65
|
def load_service_config_from_file(repo_path: str) -> ServiceConfig:
|
|
59
|
-
config_path = os.path.join(
|
|
60
|
-
repo_path, DEVSERVICES_DIR_NAME, DOCKER_COMPOSE_FILE_NAME
|
|
61
|
-
)
|
|
66
|
+
config_path = os.path.join(repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME)
|
|
62
67
|
if not os.path.exists(config_path):
|
|
63
68
|
raise ConfigNotFoundError(f"Config file not found in directory: {config_path}")
|
|
64
69
|
with open(config_path, "r") as stream:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ServiceNotFoundError(Exception):
|
|
5
|
+
"""Raised when a service is not found."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigError(Exception):
|
|
11
|
+
"""Base class for configuration-related errors."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigNotFoundError(ConfigError):
|
|
17
|
+
"""Raised when a configuration file is not found."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ConfigValidationError(ConfigError):
|
|
23
|
+
"""Raised when a configuration file is invalid."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfigParseError(ConfigError):
|
|
29
|
+
"""Raised when a configuration file cannot be parsed."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DockerComposeError(Exception):
|
|
35
|
+
"""Base class for Docker Compose related errors."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, command: str, returncode: int, stdout: str, stderr: str):
|
|
38
|
+
self.command = command
|
|
39
|
+
self.returncode = returncode
|
|
40
|
+
self.stdout = stdout
|
|
41
|
+
self.stderr = stderr
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import atexit
|
|
5
|
+
from importlib import metadata
|
|
6
|
+
|
|
7
|
+
import sentry_sdk
|
|
8
|
+
from sentry_sdk.integrations.argv import ArgvIntegration
|
|
9
|
+
|
|
10
|
+
from devservices.commands import list_dependencies
|
|
11
|
+
from devservices.commands import list_services
|
|
12
|
+
from devservices.commands import logs
|
|
13
|
+
from devservices.commands import start
|
|
14
|
+
from devservices.commands import status
|
|
15
|
+
from devservices.commands import stop
|
|
16
|
+
|
|
17
|
+
sentry_sdk.init(
|
|
18
|
+
dsn="https://56470da7302c16e83141f62f88e46449@o1.ingest.us.sentry.io/4507946704961536",
|
|
19
|
+
traces_sample_rate=1.0,
|
|
20
|
+
profiles_sample_rate=1.0,
|
|
21
|
+
enable_tracing=True,
|
|
22
|
+
integrations=[ArgvIntegration()],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@atexit.register
|
|
27
|
+
def cleanup() -> None:
|
|
28
|
+
sentry_sdk.flush()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> None:
|
|
32
|
+
parser = argparse.ArgumentParser(
|
|
33
|
+
prog="devservices",
|
|
34
|
+
description="CLI tool for managing service dependencies.",
|
|
35
|
+
usage="devservices [-h] [--version] COMMAND ...",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--version", action="version", version=metadata.version("devservices")
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
subparsers = parser.add_subparsers(dest="command", title="commands", metavar="")
|
|
42
|
+
|
|
43
|
+
# Add subparsers for each command
|
|
44
|
+
start.add_parser(subparsers)
|
|
45
|
+
stop.add_parser(subparsers)
|
|
46
|
+
list_dependencies.add_parser(subparsers)
|
|
47
|
+
list_services.add_parser(subparsers)
|
|
48
|
+
status.add_parser(subparsers)
|
|
49
|
+
logs.add_parser(subparsers)
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
if args.command:
|
|
54
|
+
# Call the appropriate function based on the command
|
|
55
|
+
with sentry_sdk.start_transaction(op="command", name=args.command):
|
|
56
|
+
args.func(args)
|
|
57
|
+
else:
|
|
58
|
+
parser.print_help()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
main()
|
|
@@ -3,12 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
|
-
from configs.service_config import ServiceConfig
|
|
7
|
-
from exceptions import ConfigNotFoundError
|
|
8
|
-
from exceptions import ConfigParseError
|
|
9
|
-
from exceptions import ConfigValidationError
|
|
10
|
-
from exceptions import ServiceNotFoundError
|
|
11
|
-
from utils.devenv import get_coderoot
|
|
6
|
+
from devservices.configs.service_config import ServiceConfig
|
|
7
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
8
|
+
from devservices.exceptions import ConfigParseError
|
|
9
|
+
from devservices.exceptions import ConfigValidationError
|
|
10
|
+
from devservices.exceptions import ServiceNotFoundError
|
|
11
|
+
from devservices.utils.devenv import get_coderoot
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass
|
|
@@ -20,7 +20,7 @@ class Service:
|
|
|
20
20
|
|
|
21
21
|
def get_local_services(coderoot: str) -> list[Service]:
|
|
22
22
|
"""Get a list of services in the coderoot."""
|
|
23
|
-
from configs.service_config import load_service_config_from_file
|
|
23
|
+
from devservices.configs.service_config import load_service_config_from_file
|
|
24
24
|
|
|
25
25
|
services = []
|
|
26
26
|
for repo in os.listdir(coderoot):
|
|
@@ -43,7 +43,7 @@ def get_local_services(coderoot: str) -> list[Service]:
|
|
|
43
43
|
def find_matching_service(service_name: str | None = None) -> Service:
|
|
44
44
|
"""Find a service with the given name."""
|
|
45
45
|
if service_name is None:
|
|
46
|
-
from configs.service_config import load_service_config_from_file
|
|
46
|
+
from devservices.configs.service_config import load_service_config_from_file
|
|
47
47
|
|
|
48
48
|
repo_path = os.getcwd()
|
|
49
49
|
service_config = load_service_config_from_file(repo_path)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.cfg
|
|
4
|
+
devservices/__init__.py
|
|
5
|
+
devservices/constants.py
|
|
6
|
+
devservices/exceptions.py
|
|
7
|
+
devservices/main.py
|
|
8
|
+
devservices.egg-info/PKG-INFO
|
|
9
|
+
devservices.egg-info/SOURCES.txt
|
|
10
|
+
devservices.egg-info/dependency_links.txt
|
|
11
|
+
devservices.egg-info/entry_points.txt
|
|
12
|
+
devservices.egg-info/requires.txt
|
|
13
|
+
devservices.egg-info/top_level.txt
|
|
14
|
+
devservices/commands/__init__.py
|
|
15
|
+
devservices/commands/list_dependencies.py
|
|
16
|
+
devservices/commands/list_services.py
|
|
17
|
+
devservices/commands/logs.py
|
|
18
|
+
devservices/commands/start.py
|
|
19
|
+
devservices/commands/status.py
|
|
20
|
+
devservices/commands/stop.py
|
|
21
|
+
devservices/configs/service_config.py
|
|
22
|
+
devservices/utils/__init__.py
|
|
23
|
+
devservices/utils/console.py
|
|
24
|
+
devservices/utils/devenv.py
|
|
25
|
+
devservices/utils/docker_compose.py
|
|
26
|
+
devservices/utils/services.py
|
|
27
|
+
tests/__init__.py
|
|
28
|
+
tests/conftest.py
|
|
29
|
+
tests/testutils.py
|
|
30
|
+
tests/commands/test_start.py
|
|
31
|
+
tests/commands/test_stop.py
|
|
32
|
+
tests/configs/test_service_config.py
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=70", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devservices"
|
|
7
|
-
version = "0.0.
|
|
8
|
-
|
|
7
|
+
version = "0.0.3"
|
|
8
|
+
# 3.10 is just for internal pypi compat
|
|
9
|
+
requires-python = ">=3.10"
|
|
9
10
|
dependencies = [
|
|
10
11
|
"pyyaml",
|
|
11
12
|
"sentry-devenv",
|
|
@@ -21,12 +22,11 @@ dev = [
|
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
[project.scripts]
|
|
24
|
-
devservices = "main:main"
|
|
25
|
+
devservices = "devservices.main:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages]
|
|
28
|
+
find = {}
|
|
25
29
|
|
|
26
|
-
[tool.setuptools]
|
|
27
|
-
package-dir = {"" = "src"}
|
|
28
|
-
packages = {find = {where = ["src"]}}
|
|
29
|
-
include-package-data = true
|
|
30
30
|
|
|
31
31
|
[tool.mypy]
|
|
32
32
|
python_version = "3.12"
|
|
File without changes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from argparse import Namespace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest import mock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from devservices.commands.start import start
|
|
12
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
13
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
14
|
+
from tests.testutils import create_config_file
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mock.patch("devservices.utils.docker_compose.subprocess.run")
|
|
18
|
+
def test_start_simple(mock_run: mock.Mock, tmp_path: Path) -> None:
|
|
19
|
+
config = {
|
|
20
|
+
"x-sentry-service-config": {
|
|
21
|
+
"version": 0.1,
|
|
22
|
+
"service_name": "example-service",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"redis": {"description": "Redis"},
|
|
25
|
+
"clickhouse": {"description": "Clickhouse"},
|
|
26
|
+
},
|
|
27
|
+
"modes": {"default": ["redis", "clickhouse"]},
|
|
28
|
+
},
|
|
29
|
+
"services": {
|
|
30
|
+
"redis": {"image": "redis:6.2.14-alpine"},
|
|
31
|
+
"clickhouse": {
|
|
32
|
+
"image": "altinity/clickhouse-server:23.8.11.29.altinitystable"
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
create_config_file(tmp_path, config)
|
|
37
|
+
os.chdir(tmp_path)
|
|
38
|
+
|
|
39
|
+
args = Namespace(service_name=None)
|
|
40
|
+
start(args)
|
|
41
|
+
|
|
42
|
+
mock_run.assert_called_once_with(
|
|
43
|
+
[
|
|
44
|
+
"docker",
|
|
45
|
+
"compose",
|
|
46
|
+
"-f",
|
|
47
|
+
f"{tmp_path}/{DEVSERVICES_DIR_NAME}/{CONFIG_FILE_NAME}",
|
|
48
|
+
"up",
|
|
49
|
+
"-d",
|
|
50
|
+
"redis",
|
|
51
|
+
"clickhouse",
|
|
52
|
+
],
|
|
53
|
+
check=True,
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@mock.patch("devservices.utils.docker_compose.subprocess.run")
|
|
60
|
+
def test_start_error(
|
|
61
|
+
mock_run: mock.Mock, capsys: pytest.CaptureFixture[str], tmp_path: Path
|
|
62
|
+
) -> None:
|
|
63
|
+
mock_run.side_effect = subprocess.CalledProcessError(
|
|
64
|
+
returncode=1, stderr="Docker Compose error", cmd=""
|
|
65
|
+
)
|
|
66
|
+
config = {
|
|
67
|
+
"x-sentry-service-config": {
|
|
68
|
+
"version": 0.1,
|
|
69
|
+
"service_name": "example-service",
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"redis": {"description": "Redis"},
|
|
72
|
+
"clickhouse": {"description": "Clickhouse"},
|
|
73
|
+
},
|
|
74
|
+
"modes": {"default": ["redis", "clickhouse"]},
|
|
75
|
+
},
|
|
76
|
+
"services": {
|
|
77
|
+
"redis": {"image": "redis:6.2.14-alpine"},
|
|
78
|
+
"clickhouse": {
|
|
79
|
+
"image": "altinity/clickhouse-server:23.8.11.29.altinitystable"
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
create_config_file(tmp_path, config)
|
|
85
|
+
os.chdir(tmp_path)
|
|
86
|
+
|
|
87
|
+
args = Namespace(service_name=None)
|
|
88
|
+
|
|
89
|
+
with pytest.raises(SystemExit):
|
|
90
|
+
start(args)
|
|
91
|
+
|
|
92
|
+
# Capture the printed output
|
|
93
|
+
captured = capsys.readouterr()
|
|
94
|
+
|
|
95
|
+
assert (
|
|
96
|
+
"Failed to start example-service: Docker Compose error" in captured.out.strip()
|
|
97
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from argparse import Namespace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest import mock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from devservices.commands.stop import stop
|
|
12
|
+
from devservices.constants import CONFIG_FILE_NAME
|
|
13
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
14
|
+
from tests.testutils import create_config_file
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mock.patch("devservices.utils.docker_compose.subprocess.run")
|
|
18
|
+
def test_stop_simple(mock_run: mock.Mock, tmp_path: Path) -> None:
|
|
19
|
+
config = {
|
|
20
|
+
"x-sentry-service-config": {
|
|
21
|
+
"version": 0.1,
|
|
22
|
+
"service_name": "example-service",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"redis": {"description": "Redis"},
|
|
25
|
+
"clickhouse": {"description": "Clickhouse"},
|
|
26
|
+
},
|
|
27
|
+
"modes": {"default": ["redis", "clickhouse"]},
|
|
28
|
+
},
|
|
29
|
+
"services": {
|
|
30
|
+
"redis": {"image": "redis:6.2.14-alpine"},
|
|
31
|
+
"clickhouse": {
|
|
32
|
+
"image": "altinity/clickhouse-server:23.8.11.29.altinitystable"
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
create_config_file(tmp_path, config)
|
|
37
|
+
os.chdir(tmp_path)
|
|
38
|
+
|
|
39
|
+
args = Namespace(service_name=None)
|
|
40
|
+
|
|
41
|
+
stop(args)
|
|
42
|
+
|
|
43
|
+
mock_run.assert_called_once_with(
|
|
44
|
+
[
|
|
45
|
+
"docker",
|
|
46
|
+
"compose",
|
|
47
|
+
"-f",
|
|
48
|
+
f"{tmp_path}/{DEVSERVICES_DIR_NAME}/{CONFIG_FILE_NAME}",
|
|
49
|
+
"down",
|
|
50
|
+
"redis",
|
|
51
|
+
"clickhouse",
|
|
52
|
+
],
|
|
53
|
+
check=True,
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@mock.patch("devservices.utils.docker_compose.subprocess.run")
|
|
60
|
+
def test_stop_error(
|
|
61
|
+
mock_run: mock.Mock, capsys: pytest.CaptureFixture[str], tmp_path: Path
|
|
62
|
+
) -> None:
|
|
63
|
+
mock_run.side_effect = subprocess.CalledProcessError(
|
|
64
|
+
returncode=1, stderr="Docker Compose error", cmd=""
|
|
65
|
+
)
|
|
66
|
+
config = {
|
|
67
|
+
"x-sentry-service-config": {
|
|
68
|
+
"version": 0.1,
|
|
69
|
+
"service_name": "example-service",
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"redis": {"description": "Redis"},
|
|
72
|
+
"clickhouse": {"description": "Clickhouse"},
|
|
73
|
+
},
|
|
74
|
+
"modes": {"default": ["redis", "clickhouse"]},
|
|
75
|
+
},
|
|
76
|
+
"services": {
|
|
77
|
+
"redis": {"image": "redis:6.2.14-alpine"},
|
|
78
|
+
"clickhouse": {
|
|
79
|
+
"image": "altinity/clickhouse-server:23.8.11.29.altinitystable"
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
create_config_file(tmp_path, config)
|
|
85
|
+
os.chdir(tmp_path)
|
|
86
|
+
|
|
87
|
+
args = Namespace(service_name=None)
|
|
88
|
+
|
|
89
|
+
with pytest.raises(SystemExit):
|
|
90
|
+
stop(args)
|
|
91
|
+
|
|
92
|
+
# Capture the printed output
|
|
93
|
+
captured = capsys.readouterr()
|
|
94
|
+
|
|
95
|
+
assert (
|
|
96
|
+
"Failed to stop example-service: Docker Compose error" in captured.out.strip()
|
|
97
|
+
)
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from devservices.configs.service_config import load_service_config_from_file
|
|
9
|
+
from devservices.exceptions import ConfigNotFoundError
|
|
10
|
+
from devservices.exceptions import ConfigParseError
|
|
11
|
+
from devservices.exceptions import ConfigValidationError
|
|
12
|
+
from tests.testutils import create_config_file
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize(
|
|
16
|
+
"service_name, dependencies, modes",
|
|
17
|
+
[
|
|
18
|
+
(
|
|
19
|
+
"example-service",
|
|
20
|
+
{"example-dependency": {"description": "Example dependency"}},
|
|
21
|
+
{"default": ["example-dependency"]},
|
|
22
|
+
),
|
|
23
|
+
(
|
|
24
|
+
"example-service",
|
|
25
|
+
{
|
|
26
|
+
"example-dependency-1": {
|
|
27
|
+
"description": "Example dependency 1",
|
|
28
|
+
"remote": {
|
|
29
|
+
"repo_name": "example-dependency-1",
|
|
30
|
+
"branch": "main",
|
|
31
|
+
"repo_link": "https://example.com",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"example-dependency-2": {
|
|
35
|
+
"description": "Example dependency 2",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{"default": ["example-dependency-1", "example-dependency-2"]},
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
"example-service",
|
|
42
|
+
{
|
|
43
|
+
"example-dependency-1": {
|
|
44
|
+
"description": "Example dependency 1",
|
|
45
|
+
"remote": {
|
|
46
|
+
"repo_name": "example-dependency-1",
|
|
47
|
+
"branch": "main",
|
|
48
|
+
"repo_link": "https://example.com",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"example-dependency-2": {
|
|
52
|
+
"description": "Example dependency 2",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{"default": ["example-dependency-1"], "custom": ["example-dependency-2"]},
|
|
56
|
+
),
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
def test_load_service_config_from_file(
|
|
60
|
+
tmp_path: Path,
|
|
61
|
+
service_name: str,
|
|
62
|
+
dependencies: dict[str, dict[str, object]],
|
|
63
|
+
modes: dict[str, list[str]],
|
|
64
|
+
) -> None:
|
|
65
|
+
config = {
|
|
66
|
+
"x-sentry-service-config": {
|
|
67
|
+
"version": 0.1,
|
|
68
|
+
"service_name": service_name,
|
|
69
|
+
"dependencies": {key: value for key, value in dependencies.items()},
|
|
70
|
+
"modes": {key: value for key, value in modes.items()},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
create_config_file(tmp_path, config)
|
|
74
|
+
|
|
75
|
+
service_config = load_service_config_from_file(str(tmp_path))
|
|
76
|
+
assert asdict(service_config) == {
|
|
77
|
+
"version": 0.1,
|
|
78
|
+
"service_name": service_name,
|
|
79
|
+
"dependencies": {
|
|
80
|
+
key: {
|
|
81
|
+
"description": value["description"],
|
|
82
|
+
"remote": value.get("remote"),
|
|
83
|
+
}
|
|
84
|
+
for key, value in dependencies.items()
|
|
85
|
+
},
|
|
86
|
+
"modes": modes,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_load_service_config_from_file_no_dependencies(tmp_path: Path) -> None:
|
|
91
|
+
config = {
|
|
92
|
+
"x-sentry-service-config": {
|
|
93
|
+
"version": 0.1,
|
|
94
|
+
"service_name": "example-service",
|
|
95
|
+
"modes": {"default": []},
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
create_config_file(tmp_path, config)
|
|
99
|
+
|
|
100
|
+
service_config = load_service_config_from_file(str(tmp_path))
|
|
101
|
+
assert asdict(service_config) == {
|
|
102
|
+
"version": 0.1,
|
|
103
|
+
"service_name": "example-service",
|
|
104
|
+
"dependencies": {},
|
|
105
|
+
"modes": {"default": []},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_load_service_config_from_file_missing_config(tmp_path: Path) -> None:
|
|
110
|
+
with pytest.raises(ConfigNotFoundError) as e:
|
|
111
|
+
load_service_config_from_file(str(tmp_path))
|
|
112
|
+
assert (
|
|
113
|
+
str(e.value)
|
|
114
|
+
== f"Config file not found in directory: {tmp_path / 'devservices' / 'config.yml'}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_load_service_config_from_file_invalid_version(tmp_path: Path) -> None:
|
|
119
|
+
config = {
|
|
120
|
+
"x-sentry-service-config": {
|
|
121
|
+
"version": 0.2,
|
|
122
|
+
"service_name": "example-service",
|
|
123
|
+
"dependencies": {
|
|
124
|
+
"example-dependency": {"description": "Example dependency"}
|
|
125
|
+
},
|
|
126
|
+
"modes": {"default": ["example-dependency"]},
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
create_config_file(tmp_path, config)
|
|
130
|
+
|
|
131
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
132
|
+
load_service_config_from_file(str(tmp_path))
|
|
133
|
+
assert str(e.value) == "Invalid version '0.2' in service config"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_load_service_config_from_file_missing_version(tmp_path: Path) -> None:
|
|
137
|
+
config = {
|
|
138
|
+
"x-sentry-service-config": {
|
|
139
|
+
"dependencies": {
|
|
140
|
+
"example-dependency": {"description": "Example dependency"}
|
|
141
|
+
},
|
|
142
|
+
"modes": {"default": ["example-dependency"]},
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
create_config_file(tmp_path, config)
|
|
146
|
+
|
|
147
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
148
|
+
load_service_config_from_file(str(tmp_path))
|
|
149
|
+
assert str(e.value) == "Version is required in service config"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_load_service_config_from_file_missing_service_name(tmp_path: Path) -> None:
|
|
153
|
+
config = {
|
|
154
|
+
"x-sentry-service-config": {
|
|
155
|
+
"version": 0.1,
|
|
156
|
+
"dependencies": {
|
|
157
|
+
"example-dependency": {"description": "Example dependency"}
|
|
158
|
+
},
|
|
159
|
+
"modes": {"default": ["example-dependency"]},
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
create_config_file(tmp_path, config)
|
|
163
|
+
|
|
164
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
165
|
+
load_service_config_from_file(str(tmp_path))
|
|
166
|
+
assert str(e.value) == "Service name is required in service config"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_load_service_config_from_file_invalid_dependency(tmp_path: Path) -> None:
|
|
170
|
+
config = {
|
|
171
|
+
"x-sentry-service-config": {
|
|
172
|
+
"version": 0.1,
|
|
173
|
+
"service_name": "example-service",
|
|
174
|
+
"dependencies": {
|
|
175
|
+
"example-dependency": {"description": "Example dependency"}
|
|
176
|
+
},
|
|
177
|
+
"modes": {"default": ["example-dependency", "unknown-dependency"]},
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
create_config_file(tmp_path, config)
|
|
181
|
+
|
|
182
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
183
|
+
load_service_config_from_file(str(tmp_path))
|
|
184
|
+
assert (
|
|
185
|
+
str(e.value)
|
|
186
|
+
== "Service 'unknown-dependency' in mode 'default' is not defined in dependencies"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_load_service_config_from_file_missing_default_mode(tmp_path: Path) -> None:
|
|
191
|
+
config = {
|
|
192
|
+
"x-sentry-service-config": {
|
|
193
|
+
"version": 0.1,
|
|
194
|
+
"service_name": "example-service",
|
|
195
|
+
"dependencies": {
|
|
196
|
+
"example-dependency": {"description": "Example dependency"}
|
|
197
|
+
},
|
|
198
|
+
"modes": {"custom": ["example-dependency"]},
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
create_config_file(tmp_path, config)
|
|
202
|
+
|
|
203
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
204
|
+
load_service_config_from_file(str(tmp_path))
|
|
205
|
+
assert str(e.value) == "Default mode is required in service config"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_load_service_config_from_file_no_modes(tmp_path: Path) -> None:
|
|
209
|
+
config = {
|
|
210
|
+
"x-sentry-service-config": {
|
|
211
|
+
"version": 0.1,
|
|
212
|
+
"service_name": "example-service",
|
|
213
|
+
"dependencies": {
|
|
214
|
+
"example-dependency": {"description": "Example dependency"}
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
create_config_file(tmp_path, config)
|
|
219
|
+
|
|
220
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
221
|
+
load_service_config_from_file(str(tmp_path))
|
|
222
|
+
assert str(e.value) == "Default mode is required in service config"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_load_service_config_from_file_invalid_dependencies(tmp_path: Path) -> None:
|
|
226
|
+
config = {
|
|
227
|
+
"x-sentry-service-config": {
|
|
228
|
+
"version": 0.1,
|
|
229
|
+
"service_name": "example-service",
|
|
230
|
+
"dependencies": {
|
|
231
|
+
"example-dependency": {
|
|
232
|
+
"description": "Example dependency",
|
|
233
|
+
"unknown": "key",
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"modes": {"default": ["example-dependency"]},
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
create_config_file(tmp_path, config)
|
|
240
|
+
|
|
241
|
+
with pytest.raises(ConfigParseError) as e:
|
|
242
|
+
load_service_config_from_file(str(tmp_path))
|
|
243
|
+
assert (
|
|
244
|
+
str(e.value)
|
|
245
|
+
== "Error parsing service dependencies: Dependency.__init__() got an unexpected keyword argument 'unknown'"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_load_service_config_from_file_invalid_modes(tmp_path: Path) -> None:
|
|
250
|
+
config = {
|
|
251
|
+
"x-sentry-service-config": {
|
|
252
|
+
"version": 0.1,
|
|
253
|
+
"service_name": "example-service",
|
|
254
|
+
"dependencies": {
|
|
255
|
+
"example-dependency": {"description": "Example dependency"}
|
|
256
|
+
},
|
|
257
|
+
"modes": {
|
|
258
|
+
"default": ["example-dependency"],
|
|
259
|
+
"custom": "example-dependency",
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
create_config_file(tmp_path, config)
|
|
264
|
+
|
|
265
|
+
with pytest.raises(ConfigValidationError) as e:
|
|
266
|
+
load_service_config_from_file(str(tmp_path))
|
|
267
|
+
assert str(e.value) == "Services in mode 'custom' must be a list"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_load_service_config_from_file_no_x_sentry_service_config(
|
|
271
|
+
tmp_path: Path,
|
|
272
|
+
) -> None:
|
|
273
|
+
config = {
|
|
274
|
+
"x-not-sentry-service-config": {
|
|
275
|
+
"version": 0.1,
|
|
276
|
+
"service_name": "example-service",
|
|
277
|
+
"dependencies": {
|
|
278
|
+
"example-dependency": {"description": "Example dependency"}
|
|
279
|
+
},
|
|
280
|
+
"modes": {"default": ["example-dependency"]},
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
create_config_file(tmp_path, config)
|
|
284
|
+
|
|
285
|
+
with pytest.raises(ConfigParseError) as e:
|
|
286
|
+
load_service_config_from_file(str(tmp_path))
|
|
287
|
+
assert str(e.value) == "Config file does not contain 'x-sentry-service-config' key"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_load_service_config_from_file_invalid_yaml(tmp_path: Path) -> None:
|
|
291
|
+
config = """x-sentry-service-config
|
|
292
|
+
version: 0.1
|
|
293
|
+
service_name: "example-service"
|
|
294
|
+
dependencies:
|
|
295
|
+
example-dependency:
|
|
296
|
+
description: "Example dependency"
|
|
297
|
+
modes:
|
|
298
|
+
default: ["example-dependency"]"""
|
|
299
|
+
devservices_dir = Path(tmp_path, "devservices")
|
|
300
|
+
devservices_dir.mkdir(parents=True, exist_ok=True)
|
|
301
|
+
tmp_file = Path(devservices_dir, "config.yml")
|
|
302
|
+
with tmp_file.open("w") as f:
|
|
303
|
+
f.write(config)
|
|
304
|
+
|
|
305
|
+
with pytest.raises(ConfigParseError) as e:
|
|
306
|
+
load_service_config_from_file(str(tmp_path))
|
|
307
|
+
assert (
|
|
308
|
+
str(e.value)
|
|
309
|
+
== f"Error parsing config file: mapping values are not allowed here\n in \"{tmp_path / 'devservices' / 'config.yml'}\", line 2, column 12"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_load_service_config_from_file_invalid_yaml_tag(tmp_path: Path) -> None:
|
|
314
|
+
config = """x-sentry-service-config:
|
|
315
|
+
version: 0.1
|
|
316
|
+
service_name: "example-service"
|
|
317
|
+
dependencies:
|
|
318
|
+
example-dependency:
|
|
319
|
+
description: "Example dependency"
|
|
320
|
+
link: !!invalid_tag "https://example.com"
|
|
321
|
+
modes:
|
|
322
|
+
default: ["example-dependency"]"""
|
|
323
|
+
devservices_dir = Path(tmp_path, "devservices")
|
|
324
|
+
devservices_dir.mkdir(parents=True, exist_ok=True)
|
|
325
|
+
tmp_file = Path(devservices_dir, "config.yml")
|
|
326
|
+
with tmp_file.open("w") as f:
|
|
327
|
+
f.write(config)
|
|
328
|
+
|
|
329
|
+
with pytest.raises(ConfigParseError) as e:
|
|
330
|
+
load_service_config_from_file(str(tmp_path))
|
|
331
|
+
assert (
|
|
332
|
+
str(e.value)
|
|
333
|
+
== f"Error parsing config file: could not determine a constructor for the tag 'tag:yaml.org,2002:invalid_tag'\n in \"{tmp_path / 'devservices' / 'config.yml'}\", line 7, column 19"
|
|
334
|
+
)
|
|
File without changes
|
|
@@ -10,6 +10,6 @@ def create_config_file(
|
|
|
10
10
|
) -> None:
|
|
11
11
|
devservices_dir = Path(tmp_path, "devservices")
|
|
12
12
|
devservices_dir.mkdir(parents=True, exist_ok=True)
|
|
13
|
-
tmp_file = Path(devservices_dir, "
|
|
13
|
+
tmp_file = Path(devservices_dir, "config.yml")
|
|
14
14
|
with tmp_file.open("w") as f:
|
|
15
15
|
yaml.dump(config, f, sort_keys=False, default_flow_style=False)
|
devservices-0.0.1/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# devservices
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
pyproject.toml
|
|
3
|
-
setup.cfg
|
|
4
|
-
src/commands/__init__.py
|
|
5
|
-
src/commands/list_dependencies.py
|
|
6
|
-
src/commands/list_services.py
|
|
7
|
-
src/commands/logs.py
|
|
8
|
-
src/commands/start.py
|
|
9
|
-
src/commands/status.py
|
|
10
|
-
src/commands/stop.py
|
|
11
|
-
src/configs/service_config.py
|
|
12
|
-
src/devservices.egg-info/PKG-INFO
|
|
13
|
-
src/devservices.egg-info/SOURCES.txt
|
|
14
|
-
src/devservices.egg-info/dependency_links.txt
|
|
15
|
-
src/devservices.egg-info/entry_points.txt
|
|
16
|
-
src/devservices.egg-info/requires.txt
|
|
17
|
-
src/devservices.egg-info/top_level.txt
|
|
18
|
-
src/utils/__init__.py
|
|
19
|
-
src/utils/console.py
|
|
20
|
-
src/utils/devenv.py
|
|
21
|
-
src/utils/docker_compose.py
|
|
22
|
-
src/utils/services.py
|
|
23
|
-
tests/testutils.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|