diff --git a/devservices/commands/down.py b/devservices/commands/down.py index 8d34134c..0836ecb6 100644 --- a/devservices/commands/down.py +++ b/devservices/commands/down.py @@ -11,6 +11,7 @@ from devservices.constants import CONFIG_FILE_NAME from devservices.constants import DEPENDENCY_CONFIG_VERSION +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY from devservices.constants import DEVSERVICES_DIR_NAME @@ -23,7 +24,6 @@ from devservices.utils.console import Status from devservices.utils.dependencies import construct_dependency_graph from devservices.utils.dependencies import DependencyNode -from devservices.utils.dependencies import DependencyType from devservices.utils.dependencies import get_non_shared_remote_dependencies from devservices.utils.dependencies import install_and_verify_dependencies from devservices.utils.dependencies import InstalledRemoteDependency diff --git a/devservices/commands/reset.py b/devservices/commands/reset.py index 1e936c5f..f0c909a0 100644 --- a/devservices/commands/reset.py +++ b/devservices/commands/reset.py @@ -7,6 +7,7 @@ from sentry_sdk import capture_exception from devservices.commands.down import down +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_ORCHESTRATOR_LABEL from devservices.exceptions import DockerDaemonNotRunningError from devservices.exceptions import DockerError @@ -14,7 +15,6 @@ from devservices.utils.console import Status from devservices.utils.dependencies import construct_dependency_graph from devservices.utils.dependencies import DependencyNode -from devservices.utils.dependencies import DependencyType from devservices.utils.docker import get_matching_containers from devservices.utils.docker import get_volumes_for_containers from devservices.utils.docker import remove_docker_resources diff --git a/devservices/commands/status.py b/devservices/commands/status.py index f04f4f58..59db155b 100644 --- a/devservices/commands/status.py +++ b/devservices/commands/status.py @@ -15,6 +15,7 @@ from devservices.constants import Color from devservices.constants import CONFIG_FILE_NAME from devservices.constants import DEPENDENCY_CONFIG_VERSION +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY from devservices.constants import DEVSERVICES_DIR_NAME @@ -27,7 +28,6 @@ from devservices.utils.dependencies import construct_dependency_graph from devservices.utils.dependencies import DependencyGraph from devservices.utils.dependencies import DependencyNode -from devservices.utils.dependencies import DependencyType from devservices.utils.dependencies import install_and_verify_dependencies from devservices.utils.dependencies import InstalledRemoteDependency from devservices.utils.docker_compose import get_docker_compose_commands_to_run diff --git a/devservices/commands/up.py b/devservices/commands/up.py index 19655917..14db4246 100644 --- a/devservices/commands/up.py +++ b/devservices/commands/up.py @@ -11,6 +11,7 @@ from devservices.constants import CONFIG_FILE_NAME from devservices.constants import DEPENDENCY_CONFIG_VERSION +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY from devservices.constants import DEVSERVICES_DIR_NAME @@ -25,7 +26,6 @@ from devservices.utils.console import Status from devservices.utils.dependencies import construct_dependency_graph from devservices.utils.dependencies import DependencyNode -from devservices.utils.dependencies import DependencyType from devservices.utils.dependencies import install_and_verify_dependencies from devservices.utils.dependencies import InstalledRemoteDependency from devservices.utils.docker import check_all_containers_healthy diff --git a/devservices/configs/service_config.py b/devservices/configs/service_config.py index 62e459b6..3b6a32de 100644 --- a/devservices/configs/service_config.py +++ b/devservices/configs/service_config.py @@ -5,12 +5,16 @@ from dataclasses import fields import yaml +from supervisor.options import ServerOptions from devservices.constants import CONFIG_FILE_NAME +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DIR_NAME +from devservices.constants import PROGRAMS_CONF_FILE_NAME from devservices.exceptions import ConfigNotFoundError from devservices.exceptions import ConfigParseError from devservices.exceptions import ConfigValidationError +from devservices.utils.supervisor import SupervisorManager VALID_VERSIONS = [0.1] @@ -26,6 +30,7 @@ class RemoteConfig: @dataclass class Dependency: description: str + dependency_type: DependencyType remote: RemoteConfig | None = None @@ -86,6 +91,10 @@ def load_service_config_from_file(repo_path: str) -> ServiceConfig: docker_compose_services = config.get("services", {}).keys() + supervisor_programs = load_supervisor_programs_from_file( + repo_path, service_config_data.get("service_name") + ) + valid_dependency_keys = {field.name for field in fields(Dependency)} dependencies = {} @@ -97,27 +106,33 @@ def load_service_config_from_file(repo_path: str) -> ServiceConfig: raise ConfigParseError( f"Unexpected key(s) in dependency '{key}': {unexpected_keys}" ) + if value.get("remote") is None: + if key in supervisor_programs: + print("lol") + dependency_type = DependencyType.SUPERVISOR + elif key in docker_compose_services: + dependency_type = DependencyType.COMPOSE + else: + raise ConfigValidationError( + f"Dependency '{key}' is not remote but is not defined in docker-compose services or programs file" + ) + else: + dependency_type = DependencyType.SERVICE + dependencies[key] = Dependency( description=value.get("description"), - remote=RemoteConfig(**value.get("remote")) - if "remote" in value - else None, + remote=( + RemoteConfig(**value.get("remote")) + if "remote" in value + else None + ), + dependency_type=dependency_type, ) except TypeError as type_error: raise ConfigParseError( f"Error parsing service dependencies: {type_error}" ) from type_error - # Validate that all non-remote dependencies are defined in docker-compose services - for dependency_name, dependency in dependencies.items(): - if ( - dependency.remote is None - and dependency_name not in docker_compose_services - ): - raise ConfigValidationError( - f"Dependency '{dependency_name}' is not remote but is not defined in docker-compose services" - ) - service_config = ServiceConfig( version=service_config_data.get("version"), service_name=service_config_data.get("service_name"), @@ -126,3 +141,16 @@ def load_service_config_from_file(repo_path: str) -> ServiceConfig: ) return service_config + + +def load_supervisor_programs_from_file(repo_path: str, service_name: str) -> set[str]: + programs_config_path = os.path.join( + repo_path, DEVSERVICES_DIR_NAME, PROGRAMS_CONF_FILE_NAME + ) + if not os.path.exists(programs_config_path): + return set() + manager = SupervisorManager(programs_config_path, service_name=service_name) + opts = ServerOptions() + opts.configfile = manager.config_file_path + opts.process_config() + return set([program.name for program in opts.process_group_configs]) diff --git a/devservices/constants.py b/devservices/constants.py index 80e8433b..bdbd7666 100644 --- a/devservices/constants.py +++ b/devservices/constants.py @@ -2,6 +2,7 @@ import os from datetime import timedelta +from enum import StrEnum class Color: @@ -15,6 +16,12 @@ class Color: RESET = "\033[0m" +class DependencyType(StrEnum): + SERVICE = "service" + COMPOSE = "compose" + SUPERVISOR = "supervisor" + + MINIMUM_DOCKER_COMPOSE_VERSION = "2.29.7" DEVSERVICES_DIR_NAME = "devservices" CONFIG_FILE_NAME = "config.yml" diff --git a/devservices/utils/dependencies.py b/devservices/utils/dependencies.py index 018fe714..6fe0b8d8 100644 --- a/devservices/utils/dependencies.py +++ b/devservices/utils/dependencies.py @@ -10,7 +10,6 @@ from concurrent.futures import as_completed from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass -from enum import Enum from typing import TextIO from typing import TypeGuard @@ -24,6 +23,7 @@ from devservices.constants import CONFIG_FILE_NAME from devservices.constants import DEPENDENCY_CONFIG_VERSION from devservices.constants import DEPENDENCY_GIT_PARTIAL_CLONE_CONFIG_OPTIONS +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR from devservices.constants import DEVSERVICES_DIR_NAME from devservices.constants import LOGGER_NAME @@ -55,11 +55,6 @@ ] -class DependencyType(str, Enum): - SERVICE = "service" - COMPOSE = "compose" - - @dataclass(frozen=True, eq=True) class DependencyNode: name: str @@ -760,10 +755,7 @@ def _construct_dependency_graph( dependency_type=DependencyType.SERVICE, ), DependencyNode( - name=dependency_name, - dependency_type=DependencyType.SERVICE - if _has_remote_config(dependency.remote) - else DependencyType.COMPOSE, + name=dependency_name, dependency_type=dependency.dependency_type ), ) if _has_remote_config(dependency.remote): diff --git a/devservices/utils/state.py b/devservices/utils/state.py index 8f11189f..3c3f1d67 100644 --- a/devservices/utils/state.py +++ b/devservices/utils/state.py @@ -3,13 +3,14 @@ import os import sqlite3 from enum import Enum +from enum import StrEnum from typing import Literal from devservices.constants import DEVSERVICES_LOCAL_DIR from devservices.constants import STATE_DB_FILE -class ServiceRuntime(str, Enum): +class ServiceRuntime(StrEnum): LOCAL = "local" CONTAINERIZED = "containerized" diff --git a/tests/commands/test_down.py b/tests/commands/test_down.py index 36b7ee67..125c205a 100644 --- a/tests/commands/test_down.py +++ b/tests/commands/test_down.py @@ -13,6 +13,7 @@ from devservices.configs.service_config import RemoteConfig from devservices.configs.service_config import ServiceConfig from devservices.constants import CONFIG_FILE_NAME +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DIR_NAME from devservices.exceptions import ConfigError from devservices.exceptions import ServiceNotFoundError @@ -632,6 +633,7 @@ def test_down_does_not_stop_service_being_used_by_another_service( dependencies={ "redis": Dependency( description="Redis", + dependency_type=DependencyType.SERVICE, remote=RemoteConfig( repo_name="redis", repo_link=f"file://{redis_repo_path}", @@ -641,6 +643,7 @@ def test_down_does_not_stop_service_being_used_by_another_service( ), "example-service": Dependency( description="Example service", + dependency_type=DependencyType.SERVICE, remote=RemoteConfig( repo_name="example-service", repo_link=f"file://{example_repo_path}", @@ -814,6 +817,7 @@ def test_down_does_not_stop_nested_service_being_used_by_another_service( dependencies={ "parent-service": Dependency( description="Parent service", + dependency_type=DependencyType.SERVICE, remote=RemoteConfig( repo_name="parent-service", repo_link=f"file://{parent_repo_path}", @@ -1094,6 +1098,7 @@ def test_down_local_service_with_dependent_service_running( dependencies={ "redis": Dependency( description="Redis", + dependency_type=DependencyType.SERVICE, remote=RemoteConfig( repo_name="redis", repo_link=f"file://{redis_repo_path}", @@ -1103,6 +1108,7 @@ def test_down_local_service_with_dependent_service_running( ), "local-runtime-service": Dependency( description="Local runtime service", + dependency_type=DependencyType.SERVICE, remote=RemoteConfig( repo_name="local-runtime-service", repo_link=f"file://{local_runtime_repo_path}", diff --git a/tests/commands/test_list_dependencies.py b/tests/commands/test_list_dependencies.py index d866d6a1..84e247fd 100644 --- a/tests/commands/test_list_dependencies.py +++ b/tests/commands/test_list_dependencies.py @@ -10,6 +10,7 @@ from devservices.commands.list_dependencies import list_dependencies from devservices.configs.service_config import Dependency from devservices.configs.service_config import ServiceConfig +from devservices.constants import DependencyType from devservices.exceptions import ConfigValidationError from devservices.exceptions import ServiceNotFoundError from devservices.utils.services import Service @@ -115,8 +116,12 @@ def test_list_dependencies_with_dependencies( version=0.1, service_name="test-service", dependencies={ - "redis": Dependency(description="Redis"), - "postgres": Dependency(description="Postgres"), + "redis": Dependency( + description="Redis", dependency_type=DependencyType.COMPOSE + ), + "postgres": Dependency( + description="Postgres", dependency_type=DependencyType.COMPOSE + ), }, modes={"default": ["redis", "postgres"]}, ), diff --git a/tests/commands/test_list_services.py b/tests/commands/test_list_services.py index ed0b8360..8baa76c0 100644 --- a/tests/commands/test_list_services.py +++ b/tests/commands/test_list_services.py @@ -161,7 +161,7 @@ def test_list_running_services_config_error( assert ( captured.out - == "\x1b[0;33mexample-service was found with an invalid config\x1b[0m\n\x1b[0;31mDependency 'clickhouse' is not remote but is not defined in docker-compose services\x1b[0m\n" + == "\x1b[0;33mexample-service was found with an invalid config\x1b[0m\n\x1b[0;31mDependency 'clickhouse' is not remote but is not defined in docker-compose services or programs file\x1b[0m\n" ) diff --git a/tests/commands/test_logs.py b/tests/commands/test_logs.py index b5418ef0..9eda8ad5 100644 --- a/tests/commands/test_logs.py +++ b/tests/commands/test_logs.py @@ -12,6 +12,7 @@ from devservices.configs.service_config import Dependency from devservices.configs.service_config import ServiceConfig from devservices.constants import CONFIG_FILE_NAME +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DIR_NAME from devservices.exceptions import ConfigError from devservices.exceptions import ServiceNotFoundError @@ -42,8 +43,12 @@ def test_logs_no_specified_service_not_running( version=0.1, service_name="example-service", dependencies={ - "redis": Dependency(description="Redis"), - "clickhouse": Dependency(description="Clickhouse"), + "redis": Dependency( + description="Redis", dependency_type=DependencyType.COMPOSE + ), + "clickhouse": Dependency( + description="Clickhouse", dependency_type=DependencyType.COMPOSE + ), }, modes={"default": ["redis", "clickhouse"]}, ), @@ -98,8 +103,12 @@ def test_logs_no_specified_service_success( version=0.1, service_name="example-service", dependencies={ - "redis": Dependency(description="Redis"), - "clickhouse": Dependency(description="Clickhouse"), + "redis": Dependency( + description="Redis", dependency_type=DependencyType.COMPOSE + ), + "clickhouse": Dependency( + description="Clickhouse", dependency_type=DependencyType.COMPOSE + ), }, modes={"default": ["redis", "clickhouse"]}, ), diff --git a/tests/commands/test_status.py b/tests/commands/test_status.py index cf21a3c5..52a577b3 100644 --- a/tests/commands/test_status.py +++ b/tests/commands/test_status.py @@ -20,13 +20,13 @@ from devservices.configs.service_config import ServiceConfig from devservices.constants import Color from devservices.constants import CONFIG_FILE_NAME +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DIR_NAME from devservices.exceptions import DependencyError from devservices.exceptions import DockerComposeError from devservices.exceptions import ServiceNotFoundError from devservices.utils.dependencies import DependencyGraph from devservices.utils.dependencies import DependencyNode -from devservices.utils.dependencies import DependencyType from devservices.utils.services import Service from devservices.utils.state import State from devservices.utils.state import StateTables @@ -79,8 +79,14 @@ def test_get_status_json_results( version=0.1, service_name="test-service", dependencies={ - "redis": Dependency(description="Redis"), - "clickhouse": Dependency(description="Clickhouse"), + "redis": Dependency( + description="Redis", + dependency_type=DependencyType.COMPOSE, + ), + "clickhouse": Dependency( + description="Clickhouse", + dependency_type=DependencyType.COMPOSE, + ), }, modes={"default": ["redis", "clickhouse"], "test": ["redis"]}, ), @@ -541,8 +547,14 @@ def test_handle_started_service( version=0.1, service_name="test-service", dependencies={ - "redis": Dependency(description="Redis"), - "clickhouse": Dependency(description="Clickhouse"), + "redis": Dependency( + description="Redis", + dependency_type=DependencyType.COMPOSE, + ), + "clickhouse": Dependency( + description="Clickhouse", + dependency_type=DependencyType.COMPOSE, + ), }, modes={"default": ["redis", "clickhouse"], "test": ["redis"]}, ), diff --git a/tests/commands/test_toggle.py b/tests/commands/test_toggle.py index f397ee61..f15752f2 100644 --- a/tests/commands/test_toggle.py +++ b/tests/commands/test_toggle.py @@ -17,6 +17,7 @@ from devservices.configs.service_config import RemoteConfig from devservices.configs.service_config import ServiceConfig from devservices.constants import CONFIG_FILE_NAME +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DIR_NAME from devservices.exceptions import CannotToggleNonRemoteServiceError from devservices.exceptions import ConfigNotFoundError @@ -333,6 +334,7 @@ def test_toggle_dependent_service_running( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), "example-service": Dependency( description="Example service", @@ -342,6 +344,7 @@ def test_toggle_dependent_service_running( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["redis", "example-service"]}, @@ -416,6 +419,7 @@ def test_toggle_to_local_runtime_no_runtime_specified( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -439,6 +443,7 @@ def test_toggle_to_local_runtime_no_runtime_specified( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -468,6 +473,7 @@ def test_toggle_cannot_toggle_non_remote_service( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -497,6 +503,7 @@ def test_toggle_cannot_toggle_non_remote_service( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -536,6 +543,7 @@ def test_handle_transition_to_local_runtime_currently_running_standalone( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -616,6 +624,7 @@ def test_handle_transition_to_local_runtime_naming_conflict( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -640,6 +649,7 @@ def test_handle_transition_to_local_runtime_naming_conflict( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -694,6 +704,7 @@ def test_handle_transition_to_containerized_runtime_no_dependent_services( "redis": Dependency( description="Redis", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["redis"]}, @@ -745,7 +756,11 @@ def test_handle_transition_to_containerized_runtime_with_service_running( version=0.1, service_name="redis", dependencies={ - "redis": Dependency(description="Redis", remote=None), + "redis": Dependency( + description="Redis", + remote=None, + dependency_type=DependencyType.COMPOSE, + ), }, modes={"default": ["redis"]}, ), @@ -872,6 +887,7 @@ def test_handle_transition_to_containerized_runtime_with_dependent_services( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), "example-service": Dependency( description="Example service", @@ -881,6 +897,7 @@ def test_handle_transition_to_containerized_runtime_with_dependent_services( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["redis", "example-service"]}, @@ -911,10 +928,12 @@ def test_handle_transition_to_containerized_runtime_with_dependent_services( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["redis", "clickhouse"]}, @@ -1162,6 +1181,7 @@ def test_bring_down_containerized_service_install_and_verify_dependencies_failur "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -1214,6 +1234,7 @@ def test_bring_down_containerized_service_no_remote_dependencies( "clickhouse": Dependency( description="Clickhouse", remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, @@ -1230,7 +1251,11 @@ def test_bring_down_containerized_service_no_remote_dependencies( version=0.1, service_name="example-service", dependencies={ - "clickhouse": Dependency(description="Clickhouse", remote=None), + "clickhouse": Dependency( + description="Clickhouse", + remote=None, + dependency_type=DependencyType.COMPOSE, + ), }, modes={"default": ["clickhouse"]}, ), @@ -1319,7 +1344,11 @@ def test_bring_down_containerized_service_with_remote_dependency( version=0.1, service_name="example-service", dependencies={ - "clickhouse": Dependency(description="Clickhouse", remote=None), + "clickhouse": Dependency( + description="Clickhouse", + remote=None, + dependency_type=DependencyType.COMPOSE, + ), "redis": Dependency( description="Redis", remote=RemoteConfig( @@ -1328,6 +1357,7 @@ def test_bring_down_containerized_service_with_remote_dependency( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["clickhouse", "redis"]}, @@ -1347,7 +1377,9 @@ def test_bring_down_containerized_service_with_remote_dependency( service_name="example-service", dependencies={ "clickhouse": Dependency( - description="Clickhouse", remote=None + description="Clickhouse", + remote=None, + dependency_type=DependencyType.COMPOSE, ), "redis": Dependency( description="Redis", @@ -1357,6 +1389,7 @@ def test_bring_down_containerized_service_with_remote_dependency( branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["clickhouse", "redis"]}, @@ -1410,6 +1443,7 @@ def test_bring_down_containerized_service_get_non_shared_remote_dependencies_err branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["redis"]}, @@ -1434,6 +1468,7 @@ def test_bring_down_containerized_service_get_non_shared_remote_dependencies_err branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["redis"]}, @@ -1458,6 +1493,7 @@ def test_bring_down_containerized_service_get_non_shared_remote_dependencies_err branch="main", mode="default", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["redis"]}, @@ -1519,7 +1555,9 @@ def test_bring_down_containerized_service_docker_compose_error( service_name="example-service", dependencies={ "clickhouse": Dependency( - description="Clickhouse", remote=None + description="Clickhouse", + remote=None, + dependency_type=DependencyType.COMPOSE, ), }, modes={"default": ["clickhouse"]}, diff --git a/tests/configs/test_service_config.py b/tests/configs/test_service_config.py index 928bbcef..695a3ab9 100644 --- a/tests/configs/test_service_config.py +++ b/tests/configs/test_service_config.py @@ -6,19 +6,25 @@ import pytest from devservices.configs.service_config import load_service_config_from_file +from devservices.configs.service_config import load_supervisor_programs_from_file +from devservices.constants import DependencyType from devservices.exceptions import ConfigNotFoundError from devservices.exceptions import ConfigParseError from devservices.exceptions import ConfigValidationError from testing.utils import create_config_file +from testing.utils import create_programs_conf_file @pytest.mark.parametrize( - "service_name, dependencies, modes", + "service_name, dependencies, modes, dependency_types", [ ( "example-service", {"example-dependency": {"description": "Example dependency"}}, {"default": ["example-dependency"]}, + { + "example-dependency": DependencyType.COMPOSE, + }, ), ( "example-service", @@ -37,6 +43,10 @@ }, }, {"default": ["example-dependency-1", "example-dependency-2"]}, + { + "example-dependency-1": DependencyType.SERVICE, + "example-dependency-2": DependencyType.COMPOSE, + }, ), ( "example-service", @@ -55,6 +65,10 @@ }, }, {"default": ["example-dependency-1"], "custom": ["example-dependency-2"]}, + { + "example-dependency-1": DependencyType.SERVICE, + "example-dependency-2": DependencyType.COMPOSE, + }, ), ], ) @@ -63,6 +77,7 @@ def test_load_service_config_from_file( service_name: str, dependencies: dict[str, dict[str, object]], modes: dict[str, list[str]], + dependency_types: dict[str, DependencyType], ) -> None: config = { "x-sentry-service-config": { @@ -88,6 +103,7 @@ def test_load_service_config_from_file( key: { "description": value["description"], "remote": value.get("remote"), + "dependency_type": dependency_types[key], } for key, value in dependencies.items() }, @@ -309,7 +325,7 @@ def test_load_service_config_from_file_no_matching_docker_compose_service( load_service_config_from_file(str(tmp_path)) assert ( str(e.value) - == "Dependency 'example-dependency' is not remote but is not defined in docker-compose services" + == "Dependency 'example-dependency' is not remote but is not defined in docker-compose services or programs file" ) @@ -438,3 +454,90 @@ def test_load_service_config_from_file_invalid_yaml_tag(tmp_path: Path) -> None: str(e.value) == 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" ) + + +def test_load_service_config_from_file_no_programs_file(tmp_path: Path) -> None: + config = { + "x-sentry-service-config": { + "version": 0.1, + "service_name": "example-service", + "dependencies": { + "example-dependency": { + "description": "Example dependency", + }, + "example-program": { + "description": "Example program", + }, + }, + "modes": {"default": ["example-dependency", "example-program"]}, + }, + "services": { + "example-dependency": { + "image": "example-dependency", + } + }, + } + create_config_file(tmp_path, config) + + with pytest.raises(ConfigValidationError) as e: + load_service_config_from_file(str(tmp_path)) + assert ( + str(e.value) + == "Dependency 'example-program' is not remote but is not defined in docker-compose services or programs file" + ) + + +def test_load_service_config_from_file_valid_programs_file(tmp_path: Path) -> None: + devservices_config = { + "x-sentry-service-config": { + "version": 0.1, + "service_name": "example-service", + "dependencies": { + "example-dependency": { + "description": "Example dependency", + }, + "example-program": { + "description": "Example program", + }, + }, + "modes": {"default": ["example-dependency", "example-program"]}, + }, + "services": { + "example-dependency": { + "image": "example-dependency", + } + }, + } + create_config_file(tmp_path, devservices_config) + + programs_config = """[program:example-program] +command=echo "Hello, World!" +autostart=true +""" + create_programs_conf_file(tmp_path, programs_config) + + service_config = load_service_config_from_file(str(tmp_path)) + assert ( + service_config.dependencies["example-program"].dependency_type + == DependencyType.SUPERVISOR + ) + assert ( + service_config.dependencies["example-dependency"].dependency_type + == DependencyType.COMPOSE + ) + + +def test_load_supervisor_programs_from_file_no_programs_file(tmp_path: Path) -> None: + programs = load_supervisor_programs_from_file(str(tmp_path), "example-service") + assert programs == set() + + +def test_load_supervisor_programs_from_file_valid_programs_file(tmp_path: Path) -> None: + programs_config = """[program:example-program] +command=echo "Hello, World!" +autostart=true +""" + create_programs_conf_file(tmp_path, programs_config) + + programs = load_supervisor_programs_from_file(str(tmp_path), "example-service") + assert programs == {"example-program"} diff --git a/tests/utils/test_dependencies.py b/tests/utils/test_dependencies.py index f5ae14ae..af229246 100644 --- a/tests/utils/test_dependencies.py +++ b/tests/utils/test_dependencies.py @@ -15,6 +15,7 @@ from devservices.constants import CONFIG_FILE_NAME from devservices.constants import DEPENDENCY_CONFIG_VERSION from devservices.constants import DEPENDENCY_GIT_PARTIAL_CLONE_CONFIG_OPTIONS +from devservices.constants import DependencyType from devservices.constants import DEVSERVICES_DIR_NAME from devservices.exceptions import DependencyError from devservices.exceptions import DependencyNotInstalledError @@ -23,7 +24,6 @@ from devservices.exceptions import ModeDoesNotExistError from devservices.utils.dependencies import construct_dependency_graph from devservices.utils.dependencies import DependencyNode -from devservices.utils.dependencies import DependencyType from devservices.utils.dependencies import get_installed_remote_dependencies from devservices.utils.dependencies import get_non_shared_remote_dependencies from devservices.utils.dependencies import GitConfigManager @@ -203,6 +203,7 @@ def test_verify_local_dependencies_no_remote_dependencies(tmp_path: Path) -> Non ): dependency = Dependency( description="Test dependency", + dependency_type=DependencyType.COMPOSE, ) assert verify_local_dependencies([dependency]) @@ -221,6 +222,7 @@ def test_verify_local_dependencies_with_remote_dependencies(tmp_path: Path) -> N dependency = Dependency( description="Test dependency", remote=remote_config, + dependency_type=DependencyType.COMPOSE, ) assert not verify_local_dependencies([dependency]) @@ -255,6 +257,7 @@ def test_get_installed_remote_dependencies_single_dep_not_installed( branch="main", repo_link=f"file://{tmp_path / 'test-repo'}", ), + dependency_type=DependencyType.SERVICE, ) with pytest.raises(DependencyNotInstalledError): get_installed_remote_dependencies(dependencies=[mock_dependency]) @@ -273,6 +276,7 @@ def test_get_installed_remote_dependencies_single_dep_installed(tmp_path: Path) branch="main", repo_link=f"file://{tmp_path / 'test-repo'}", ), + dependency_type=DependencyType.SERVICE, ) installed_remote_dependencies_initial = install_dependencies([mock_dependency]) installed_remote_dependencies = get_installed_remote_dependencies( @@ -1963,6 +1967,7 @@ def test_install_dependencies_nested_dependency_file_contention(tmp_path: Path) branch="main", repo_link=f"file://{repo_a_path}", ), + dependency_type=DependencyType.SERVICE, ) repo_b_dependency = Dependency( description="repo b", @@ -1971,6 +1976,7 @@ def test_install_dependencies_nested_dependency_file_contention(tmp_path: Path) branch="main", repo_link=f"file://{repo_b_path}", ), + dependency_type=DependencyType.SERVICE, ) dependencies = [repo_a_dependency, repo_b_dependency] @@ -2080,6 +2086,7 @@ def test_get_non_shared_remote_dependencies_no_shared_dependencies( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ) }, modes={"default": ["dependency-1"]}, @@ -2136,6 +2143,7 @@ def test_get_non_shared_remote_dependencies_no_shared_dependencies( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ) }, modes={"default": ["dependency-1"]}, @@ -2171,6 +2179,7 @@ def test_get_non_shared_remote_dependencies_shared_dependencies( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ) }, modes={"default": ["dependency-1"]}, @@ -2200,6 +2209,7 @@ def test_get_non_shared_remote_dependencies_shared_dependencies( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ) ] ) @@ -2234,6 +2244,7 @@ def test_get_non_shared_remote_dependencies_shared_dependencies( repo_link="file://path/to/dependency-3", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "dependency-4": Dependency( description="dependency-4", @@ -2242,6 +2253,7 @@ def test_get_non_shared_remote_dependencies_shared_dependencies( repo_link="file://path/to/dependency-4", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["dependency-3"], "other": ["dependency-4"]}, @@ -2278,6 +2290,7 @@ def test_get_non_shared_remote_dependencies_nested_shared_dependencies( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "dependency-2": Dependency( description="dependency-2", @@ -2286,6 +2299,7 @@ def test_get_non_shared_remote_dependencies_nested_shared_dependencies( repo_link="file://path/to/dependency-2", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["dependency-1", "dependency-2"]}, @@ -2327,6 +2341,7 @@ def test_get_non_shared_remote_dependencies_nested_shared_dependencies( repo_link="file://path/to/dependency-3", branch="main", ), + dependency_type=DependencyType.SERVICE, ) ] ) @@ -2352,6 +2367,7 @@ def test_get_non_shared_remote_dependencies_nested_shared_dependencies( repo_link="file://path/to/dependency-3", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["dependency-3"]}, @@ -2388,6 +2404,7 @@ def test_get_non_shared_remote_dependencies_with_local_runtime_dependency( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "service-2": Dependency( description="service-2", @@ -2396,6 +2413,7 @@ def test_get_non_shared_remote_dependencies_with_local_runtime_dependency( repo_link="file://path/to/service-2", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["dependency-1", "service-2"]}, @@ -2455,6 +2473,7 @@ def test_get_non_shared_remote_dependencies_with_local_runtime_dependency( repo_link="file://path/to/dependency-3", branch="main", ), + dependency_type=DependencyType.SERVICE, ) ] ) @@ -2478,6 +2497,7 @@ def test_install_and_verify_dependencies_simple( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "dependency-2": Dependency( description="dependency-2", @@ -2486,6 +2506,7 @@ def test_install_and_verify_dependencies_simple( repo_link="file://path/to/dependency-2", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["dependency-1", "dependency-2"]}, @@ -2519,6 +2540,7 @@ def test_install_and_verify_dependencies_mode_simple( repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "dependency-2": Dependency( description="dependency-2", @@ -2527,6 +2549,7 @@ def test_install_and_verify_dependencies_mode_simple( repo_link="file://path/to/dependency-2", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={ @@ -2557,6 +2580,7 @@ def test_install_and_verify_dependencies_mode_does_not_exist(tmp_path: Path) -> repo_link="file://path/to/dependency-1", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "dependency-2": Dependency( description="dependency-2", @@ -2565,6 +2589,7 @@ def test_install_and_verify_dependencies_mode_does_not_exist(tmp_path: Path) -> repo_link="file://path/to/dependency-2", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={"default": ["dependency-1", "dependency-2"]}, @@ -2615,6 +2640,7 @@ def test_construct_dependency_graph_simple( repo_link=f"file://{dependency_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={ @@ -2729,9 +2755,11 @@ def test_construct_dependency_graph_one_nested_dependency( repo_link=f"file://{parent_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "grandparent-service": Dependency( description="grandparent-service", + dependency_type=DependencyType.COMPOSE, ), }, modes={ @@ -2877,9 +2905,11 @@ def test_construct_dependency_graph_shared_dependency( repo_link=f"file://{parent_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "grandparent-service": Dependency( description="grandparent-service", + dependency_type=DependencyType.COMPOSE, ), "child-service": Dependency( description="child-service", @@ -2888,6 +2918,7 @@ def test_construct_dependency_graph_shared_dependency( repo_link=f"file://{child_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), }, modes={ @@ -3037,9 +3068,11 @@ def test_construct_dependency_graph_non_self_reference( repo_link=f"file://{parent_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "grandparent-service-container": Dependency( description="grandparent-service-container", + dependency_type=DependencyType.COMPOSE, ), }, modes={ @@ -3252,6 +3285,7 @@ def test_construct_dependency_graph_complex( repo_link=f"file://{child_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "grandparent-service": Dependency( description="grandparent-service", @@ -3260,9 +3294,11 @@ def test_construct_dependency_graph_complex( repo_link=f"file://{grandparent_service_repo_path}", branch="main", ), + dependency_type=DependencyType.SERVICE, ), "complex-service": Dependency( description="complex-service", + dependency_type=DependencyType.COMPOSE, ), }, modes={