From 573be65d3c2a6a2debaddc7eb4333e0eaff8d9fb Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Fri, 17 Apr 2026 16:22:44 -0700 Subject: [PATCH] add per-dependency health timeout --- README.md | 2 + TODO.md | 7 ++ devservices/commands/up.py | 13 +- devservices/configs/service_config.py | 5 + devservices/constants.py | 4 +- devservices/utils/docker.py | 24 ++-- devservices/utils/docker_compose.py | 8 +- tests/commands/test_logs.py | 12 +- tests/commands/test_up.py | 163 ++++++++++++++++++++++++-- tests/configs/test_service_config.py | 1 + tests/utils/test_docker.py | 63 +++++++--- tests/utils/test_docker_compose.py | 10 +- 12 files changed, 258 insertions(+), 54 deletions(-) create mode 100644 TODO.md diff --git a/README.md b/README.md index 91fdfda..3e0b7f7 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ The configuration file is a yaml file that looks like this: # - dependencies: A list of dependencies for the service. Each dependency is a yaml block that holds the dependency configuration. There are two types of dependencies: # - local: A dependency that is defined in the config file. These dependencies do not have a remote field and must correspond to either a service defined in the 'services' section or a program defined in the 'x-programs' section. # - remote: A dependency that is defined in the devservices directory in a remote repository. These configs are automatically fetched from the remote repository and installed. Any dependency with a remote field will be treated as a remote dependency. Example: https://github.com/getsentry/snuba/blob/59a5258ccbb502827ebc1d3b1bf80c607a3301bf/devservices/config.yml#L8 +# Each dependency also supports the following optional fields: +# - healthcheck_timeout: The number of seconds to wait for this dependency's container(s) to become healthy. Defaults to 180. # - modes: A list of modes for the service. Each mode includes a list of dependencies that are used in that mode. x-sentry-service-config: version: 0.1 diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..def5170 --- /dev/null +++ b/TODO.md @@ -0,0 +1,7 @@ +# TODO + +## Per-dependency healthcheck_timeout for remote sub-services + +`healthcheck_timeout` set on a dependency only applies to that dependency's containers when it is a direct local compose service. For remote dependencies (e.g. `snuba`), the timeout is applied to all of snuba's sub-containers as a group using the calling service's fallback (`HEALTHCHECK_TIMEOUT`). There is currently no way to configure a custom timeout for an individual sub-container (e.g. `kafka` inside `snuba`) from the calling service's `config.yml`. + +To fix this properly, the timeout would need to be threaded through the remote dependency resolution path so that a service like snuba can declare its own per-sub-service timeouts in its own `config.yml` and have those respected when snuba is brought up as a dependency of another service. diff --git a/devservices/commands/up.py b/devservices/commands/up.py index 90864e9..bb3a007 100644 --- a/devservices/commands/up.py +++ b/devservices/commands/up.py @@ -17,6 +17,7 @@ from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR from devservices.constants import DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY from devservices.constants import DEVSERVICES_DIR_NAME +from devservices.constants import HEALTHCHECK_TIMEOUT from devservices.constants import DependencyType from devservices.exceptions import ConfigError from devservices.exceptions import ConfigNotFoundError @@ -33,6 +34,7 @@ from devservices.utils.dependencies import InstalledRemoteDependency from devservices.utils.dependencies import construct_dependency_graph from devservices.utils.dependencies import install_and_verify_dependencies +from devservices.utils.docker import ContainerHealthcheckConfig from devservices.utils.docker import check_all_containers_healthy from devservices.utils.docker_compose import DockerComposeCommand from devservices.utils.docker_compose import get_container_names_for_project @@ -367,7 +369,7 @@ def bring_up_docker_compose_services( mode_dependencies=mode_dependencies, ) - containers_to_check = [] + containers_to_check: list[ContainerHealthcheckConfig] = [] with concurrent.futures.ThreadPoolExecutor() as up_dependency_executor: futures = [ up_dependency_executor.submit( @@ -383,7 +385,14 @@ def bring_up_docker_compose_services( container_names = get_container_names_for_project( cmd.project_name, cmd.config_path, cmd.services ) - containers_to_check.extend(container_names) + for container in container_names: + dep = service.config.dependencies.get(container.short_name) + timeout = dep.healthcheck_timeout if dep else HEALTHCHECK_TIMEOUT + containers_to_check.append( + ContainerHealthcheckConfig( + container.name, container.short_name, timeout + ) + ) except DockerComposeError as dce: status.failure( f"Failed to get containers to healthcheck for {cmd.project_name}: {dce.stderr}" diff --git a/devservices/configs/service_config.py b/devservices/configs/service_config.py index 5c69e45..9d8d046 100644 --- a/devservices/configs/service_config.py +++ b/devservices/configs/service_config.py @@ -9,6 +9,7 @@ from devservices.constants import CONFIG_FILE_NAME from devservices.constants import DEVSERVICES_DIR_NAME +from devservices.constants import HEALTHCHECK_TIMEOUT from devservices.constants import DependencyType from devservices.exceptions import ConfigNotFoundError from devservices.exceptions import ConfigParseError @@ -32,6 +33,7 @@ class Dependency: description: str dependency_type: DependencyType remote: RemoteConfig | None = None + healthcheck_timeout: int = HEALTHCHECK_TIMEOUT @dataclass @@ -131,6 +133,9 @@ def load_service_config_from_file( else None ), dependency_type=dependency_type, + healthcheck_timeout=value.get( + "healthcheck_timeout", HEALTHCHECK_TIMEOUT + ), ) except TypeError as type_error: raise ConfigParseError( diff --git a/devservices/constants.py b/devservices/constants.py index 5b893c4..b578329 100644 --- a/devservices/constants.py +++ b/devservices/constants.py @@ -65,7 +65,7 @@ class DependencyType(StrEnum): DEVSERVICES_CACHE_DIR, "latest_version.txt" ) DEVSERVICES_LATEST_VERSION_CACHE_TTL = timedelta(minutes=15) -# Healthcheck timeout set to 2 minutes to account for slow healthchecks -HEALTHCHECK_TIMEOUT = 120 +# Healthcheck timeout set to 3 minutes to account for slow healthchecks +HEALTHCHECK_TIMEOUT = 180 HEALTHCHECK_INTERVAL = 5 SUPERVISOR_TIMEOUT = 10 diff --git a/devservices/utils/docker.py b/devservices/utils/docker.py index 6992dfd..09fa89b 100644 --- a/devservices/utils/docker.py +++ b/devservices/utils/docker.py @@ -17,9 +17,10 @@ from devservices.utils.console import Status -class ContainerNames(NamedTuple): +class ContainerHealthcheckConfig(NamedTuple): name: str short_name: str + healthcheck_timeout: int = HEALTHCHECK_TIMEOUT def check_docker_daemon_running() -> None: @@ -43,25 +44,34 @@ def check_docker_daemon_running() -> None: def check_all_containers_healthy( - status: Status, containers: list[ContainerNames] + status: Status, + containers: list[ContainerHealthcheckConfig], ) -> None: """Ensures all containers are healthy.""" - status.info("Waiting for all containers to be healthy") with concurrent.futures.ThreadPoolExecutor() as healthcheck_executor: futures = [ - healthcheck_executor.submit(wait_for_healthy, container, status) + healthcheck_executor.submit( + wait_for_healthy, container, status, container.healthcheck_timeout + ) for container in containers ] for future in concurrent.futures.as_completed(futures): future.result() -def wait_for_healthy(container: ContainerNames, status: Status) -> None: +def wait_for_healthy( + container: ContainerHealthcheckConfig, + status: Status, + timeout: int = HEALTHCHECK_TIMEOUT, +) -> None: """ Polls a Docker container's health status until it becomes healthy or a timeout is reached. """ + status.info( + f"Waiting for {container.short_name} to be healthy (timeout: {timeout}s)" + ) start = time.time() - while time.time() - start < HEALTHCHECK_TIMEOUT: + while time.time() - start < timeout: # Run docker inspect to get the container's health status try: # For containers with no healthchecks, the output will be "unknown" @@ -96,7 +106,7 @@ def wait_for_healthy(container: ContainerNames, status: Status) -> None: # If not healthy, wait and try again time.sleep(HEALTHCHECK_INTERVAL) - raise ContainerHealthcheckFailedError(container.short_name, HEALTHCHECK_TIMEOUT) + raise ContainerHealthcheckFailedError(container.short_name, timeout) @dataclass diff --git a/devservices/utils/docker_compose.py b/devservices/utils/docker_compose.py index 2cc4988..9cac6a9 100644 --- a/devservices/utils/docker_compose.py +++ b/devservices/utils/docker_compose.py @@ -26,7 +26,7 @@ from devservices.exceptions import DockerComposeInstallationError from devservices.utils.console import Console from devservices.utils.dependencies import InstalledRemoteDependency -from devservices.utils.docker import ContainerNames +from devservices.utils.docker import ContainerHealthcheckConfig from devservices.utils.docker import check_docker_daemon_running from devservices.utils.install_binary import install_binary from devservices.utils.services import Service @@ -99,7 +99,7 @@ def install_docker_compose() -> None: def get_container_names_for_project( project_name: str, config_path: str, services: list[str] -) -> list[ContainerNames]: +) -> list[ContainerHealthcheckConfig]: try: output = subprocess.check_output( [ @@ -119,7 +119,9 @@ def get_container_names_for_project( text=True, ).splitlines() return [ - ContainerNames(name=json_data["name"], short_name=json_data["short_name"]) + ContainerHealthcheckConfig( + name=json_data["name"], short_name=json_data["short_name"] + ) for line in output if (json_data := json.loads(line)) ] diff --git a/tests/commands/test_logs.py b/tests/commands/test_logs.py index b336084..d6175ef 100644 --- a/tests/commands/test_logs.py +++ b/tests/commands/test_logs.py @@ -753,10 +753,10 @@ def test_print_health_logs_all_healthy( mock_get_container_health: mock.Mock, capsys: pytest.CaptureFixture[str], ) -> None: - from devservices.utils.docker import ContainerNames + from devservices.utils.docker import ContainerHealthcheckConfig mock_get_container_names.return_value = [ - ContainerNames(name="proj-redis-1", short_name="redis") + ContainerHealthcheckConfig(name="proj-redis-1", short_name="redis") ] mock_get_container_health.return_value = [ ContainerHealth(name="proj-redis-1", status="healthy", log=[]) @@ -775,10 +775,10 @@ def test_print_health_logs_unhealthy_with_log( mock_get_container_health: mock.Mock, capsys: pytest.CaptureFixture[str], ) -> None: - from devservices.utils.docker import ContainerNames + from devservices.utils.docker import ContainerHealthcheckConfig mock_get_container_names.return_value = [ - ContainerNames(name="proj-redis-1", short_name="redis") + ContainerHealthcheckConfig(name="proj-redis-1", short_name="redis") ] mock_get_container_health.return_value = [ ContainerHealth( @@ -803,10 +803,10 @@ def test_print_health_logs_no_healthcheck( mock_get_container_health: mock.Mock, capsys: pytest.CaptureFixture[str], ) -> None: - from devservices.utils.docker import ContainerNames + from devservices.utils.docker import ContainerHealthcheckConfig mock_get_container_names.return_value = [ - ContainerNames(name="proj-redis-1", short_name="redis") + ContainerHealthcheckConfig(name="proj-redis-1", short_name="redis") ] mock_get_container_health.return_value = [ ContainerHealth(name="proj-redis-1", status="none", log=[]) diff --git a/tests/commands/test_up.py b/tests/commands/test_up.py index 8c5f870..cb80d27 100644 --- a/tests/commands/test_up.py +++ b/tests/commands/test_up.py @@ -24,6 +24,7 @@ from devservices.exceptions import ServiceNotFoundError from devservices.exceptions import SupervisorConfigError from devservices.exceptions import SupervisorError +from devservices.utils.docker import ContainerHealthcheckConfig from devservices.utils.docker_compose import DockerComposeCommand from devservices.utils.services import Service from devservices.utils.state import ServiceRuntime @@ -98,7 +99,14 @@ def test_up_simple( ) as mock_run_cmd, mock.patch( "devservices.commands.up.get_container_names_for_project", - return_value=["container1", "container2"], + return_value=[ + ContainerHealthcheckConfig( + name="container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="container2", short_name="container2" + ), + ], ) as mock_get_container_names_for_project, ): up(args) @@ -140,7 +148,7 @@ def test_up_simple( mock.ANY, ) - mock_get_container_names_for_project.assert_called_once() + mock_get_container_names_for_project.assert_called() mock_create_devservices_network.assert_called_once() @@ -472,7 +480,10 @@ def test_up_pull_error_timeout( ) @mock.patch( "devservices.commands.up.get_container_names_for_project", - return_value=["x", "y"], + return_value=[ + ContainerHealthcheckConfig(name="x", short_name="x"), + ContainerHealthcheckConfig(name="y", short_name="y"), + ], ) def test_up_pull_error_eventual_success( mock_get_container_names_for_project: mock.Mock, @@ -686,7 +697,7 @@ def test_up_docker_compose_container_lookup_error( mock.ANY, ) - mock_get_container_names_for_project.assert_called_once() + mock_get_container_names_for_project.assert_called() mock_create_devservices_network.assert_called_once() @@ -791,7 +802,14 @@ def test_up_docker_compose_container_healthcheck_failed( ) as mock_run_cmd, mock.patch( "devservices.commands.up.get_container_names_for_project", - return_value=["container1", "container2"], + return_value=[ + ContainerHealthcheckConfig( + name="container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="container2", short_name="container2" + ), + ], ) as mock_get_container_names_for_project, ): up(args) @@ -833,7 +851,7 @@ def test_up_docker_compose_container_healthcheck_failed( mock.ANY, ) - mock_get_container_names_for_project.assert_called_once() + mock_get_container_names_for_project.assert_called() mock_create_devservices_network.assert_called_once() @@ -865,11 +883,108 @@ def test_up_docker_compose_container_healthcheck_failed( assert "Starting clickhouse" in captured.out.strip() assert "Starting redis" in captured.out.strip() assert ( - "Container container1 did not become healthy within 120 seconds." + "Container container1 did not become healthy within 180 seconds." in captured.out.strip() ) +@mock.patch("devservices.utils.state.State.remove_service_entry") +@mock.patch("devservices.utils.state.State.update_service_entry") +@mock.patch("devservices.commands.up._create_devservices_network") +@mock.patch( + "devservices.utils.docker_compose.get_non_remote_services", + return_value={"clickhouse", "redis"}, +) +def test_up_per_dependency_healthcheck_timeout( + mock_get_non_remote_services: mock.Mock, + mock_create_devservices_network: mock.Mock, + mock_update_service_entry: mock.Mock, + mock_remove_service_entry: mock.Mock, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + """Each dependency's healthcheck_timeout is passed to wait_for_healthy independently.""" + with ( + mock.patch( + "devservices.commands.up.DEVSERVICES_DEPENDENCIES_CACHE_DIR", + str(tmp_path / "dependency-dir"), + ), + mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")), + ): + config = { + "x-sentry-service-config": { + "version": 0.1, + "service_name": "example-service", + "dependencies": { + "redis": {"description": "Redis"}, + "clickhouse": { + "description": "Clickhouse", + "healthcheck_timeout": 300, + }, + }, + "modes": {"default": ["redis", "clickhouse"]}, + }, + "services": { + "redis": {"image": "redis:6.2.14-alpine"}, + "clickhouse": { + "image": "altinity/clickhouse-server:23.8.11.29.altinitystable" + }, + }, + } + + service_path = tmp_path / "example-service" + create_config_file(service_path, config) + os.chdir(service_path) + + args = Namespace( + service_name=None, debug=False, mode="default", exclude_local=False + ) + + with ( + mock.patch("devservices.commands.up._pull_dependency_images"), + mock.patch( + "devservices.commands.up.run_cmd", + return_value=subprocess.CompletedProcess( + args=[], returncode=0, stdout="" + ), + ), + mock.patch( + "devservices.commands.up.get_container_names_for_project", + return_value=[ + ContainerHealthcheckConfig( + name="devservices-redis-1", short_name="redis" + ), + ContainerHealthcheckConfig( + name="devservices-clickhouse-1", short_name="clickhouse" + ), + ], + ), + mock.patch( + "devservices.utils.docker.wait_for_healthy" + ) as mock_wait_for_healthy, + ): + up(args) + + mock_wait_for_healthy.assert_any_call( + ContainerHealthcheckConfig( + name="devservices-redis-1", + short_name="redis", + healthcheck_timeout=HEALTHCHECK_TIMEOUT, + ), + mock.ANY, + HEALTHCHECK_TIMEOUT, + ) + mock_wait_for_healthy.assert_any_call( + ContainerHealthcheckConfig( + name="devservices-clickhouse-1", + short_name="clickhouse", + healthcheck_timeout=300, + ), + mock.ANY, + 300, + ) + + @mock.patch("devservices.utils.state.State.remove_service_entry") @mock.patch("devservices.utils.state.State.update_service_entry") @mock.patch("devservices.commands.up._create_devservices_network") @@ -934,7 +1049,14 @@ def test_up_mode_simple( ) as mock_run_cmd, mock.patch( "devservices.commands.up.get_container_names_for_project", - return_value=["container1", "container2"], + return_value=[ + ContainerHealthcheckConfig( + name="container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="container2", short_name="container2" + ), + ], ) as mock_get_container_names_for_project, ): up(args) @@ -974,7 +1096,7 @@ def test_up_mode_simple( mock.ANY, ) - mock_get_container_names_for_project.assert_called_once() + mock_get_container_names_for_project.assert_called() mock_create_devservices_network.assert_called_once() @@ -1164,7 +1286,14 @@ def test_up_multiple_modes( ) as mock_run_cmd, mock.patch( "devservices.commands.up.get_container_names_for_project", - return_value=["container1", "container2"], + return_value=[ + ContainerHealthcheckConfig( + name="container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="container2", short_name="container2" + ), + ], ), ): up(args) @@ -1333,7 +1462,14 @@ def test_up_multiple_modes_overlapping_running_service( ) as mock_run_cmd, mock.patch( "devservices.commands.up.get_container_names_for_project", - return_value=["container1", "container2"], + return_value=[ + ContainerHealthcheckConfig( + name="container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="container2", short_name="container2" + ), + ], ), ): up(args) @@ -1375,7 +1511,10 @@ def test_up_multiple_modes_overlapping_running_service( mock_check_all_containers_healthy.assert_called_once_with( mock.ANY, - ["container1", "container2"], + [ + ContainerHealthcheckConfig(name="container1", short_name="container1"), + ContainerHealthcheckConfig(name="container2", short_name="container2"), + ], ) captured = capsys.readouterr() diff --git a/tests/configs/test_service_config.py b/tests/configs/test_service_config.py index e25fc89..48e20b6 100644 --- a/tests/configs/test_service_config.py +++ b/tests/configs/test_service_config.py @@ -107,6 +107,7 @@ def test_load_service_config_from_file( "description": value["description"], "remote": value.get("remote"), "dependency_type": dependency_types[key], + "healthcheck_timeout": 180, } for key, value in dependencies.items() }, diff --git a/tests/utils/test_docker.py b/tests/utils/test_docker.py index 938a288..d32ed64 100644 --- a/tests/utils/test_docker.py +++ b/tests/utils/test_docker.py @@ -16,7 +16,7 @@ from devservices.exceptions import DockerDaemonNotRunningError from devservices.exceptions import DockerError from devservices.utils.docker import ContainerHealth -from devservices.utils.docker import ContainerNames +from devservices.utils.docker import ContainerHealthcheckConfig from devservices.utils.docker import HealthCheckEntry from devservices.utils.docker import check_all_containers_healthy from devservices.utils.docker import check_docker_daemon_running @@ -336,7 +336,9 @@ def test_stop_containers_remove_error( def test_wait_for_healthy_success(mock_check_output: mock.Mock) -> None: mock_status = mock.Mock() wait_for_healthy( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, ) mock_check_output.assert_called_once_with( @@ -362,7 +364,9 @@ def test_wait_for_healthy_success(mock_check_output: mock.Mock) -> None: def test_wait_for_healthy_no_healthcheck(mock_check_output: mock.Mock) -> None: mock_status = mock.Mock() wait_for_healthy( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, ) mock_check_output.assert_called_once_with( @@ -398,7 +402,9 @@ def test_wait_for_healthy_initial_check_failed_then_success( ): mock_sleep.side_effect = lambda _: frozen_time.tick(timedelta(seconds=1)) wait_for_healthy( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, ) @@ -449,7 +455,9 @@ def test_wait_for_healthy_docker_error( with freeze_time("2024-05-14 00:00:00") as frozen_time: mock_sleep.side_effect = lambda _: frozen_time.tick(timedelta(seconds=1)) wait_for_healthy( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, ) mock_check_output.assert_called_once_with( @@ -479,7 +487,9 @@ def test_wait_for_healthy_healthcheck_failed( timedelta(seconds=HEALTHCHECK_TIMEOUT / 2) ) wait_for_healthy( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, ) mock_check_output.assert_has_calls( @@ -519,24 +529,29 @@ def test_check_all_containers_healthy_success( check_all_containers_healthy( mock_status, [ - ContainerNames(name="devservices-container1", short_name="container1"), - ContainerNames(name="devservices-container2", short_name="container2"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="devservices-container2", short_name="container2" + ), ], ) - mock_status.info.assert_has_calls( - [ - mock.call("Waiting for all containers to be healthy"), - ] - ) mock_wait_for_healthy.assert_has_calls( [ mock.call( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, + HEALTHCHECK_TIMEOUT, ), mock.call( - ContainerNames(name="devservices-container2", short_name="container2"), + ContainerHealthcheckConfig( + name="devservices-container2", short_name="container2" + ), mock_status, + HEALTHCHECK_TIMEOUT, ), ] ) @@ -662,19 +677,29 @@ def test_check_all_containers_healthy_failure( check_all_containers_healthy( mock_status, [ - ContainerNames(name="devservices-container1", short_name="container1"), - ContainerNames(name="devservices-container2", short_name="container2"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="devservices-container2", short_name="container2" + ), ], ) mock_wait_for_healthy.assert_has_calls( [ mock.call( - ContainerNames(name="devservices-container1", short_name="container1"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), mock_status, + HEALTHCHECK_TIMEOUT, ), mock.call( - ContainerNames(name="devservices-container2", short_name="container2"), + ContainerHealthcheckConfig( + name="devservices-container2", short_name="container2" + ), mock_status, + HEALTHCHECK_TIMEOUT, ), ] ) diff --git a/tests/utils/test_docker_compose.py b/tests/utils/test_docker_compose.py index e6db209..e479f1e 100644 --- a/tests/utils/test_docker_compose.py +++ b/tests/utils/test_docker_compose.py @@ -15,7 +15,7 @@ from devservices.exceptions import DockerComposeInstallationError from devservices.exceptions import DockerDaemonNotRunningError from devservices.utils.dependencies import InstalledRemoteDependency -from devservices.utils.docker import ContainerNames +from devservices.utils.docker import ContainerHealthcheckConfig from devservices.utils.docker_compose import DockerComposeCommand from devservices.utils.docker_compose import check_docker_compose_version from devservices.utils.docker_compose import get_container_names_for_project @@ -781,8 +781,12 @@ def test_get_container_names_for_project_success(_mock_check_output: mock.Mock) assert get_container_names_for_project( "project", "config_path", ["container1", "container2"] ) == [ - ContainerNames(name="devservices-container1", short_name="container1"), - ContainerNames(name="devservices-container2", short_name="container2"), + ContainerHealthcheckConfig( + name="devservices-container1", short_name="container1" + ), + ContainerHealthcheckConfig( + name="devservices-container2", short_name="container2" + ), ]