Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion devservices/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ class DependencyType(StrEnum):
SUPERVISOR = "supervisor"


MINIMUM_DOCKER_COMPOSE_VERSION = "5.6.0"
# Any installed Docker Compose at or above the minimum is used as is. The install
# version is only downloaded when Compose is missing or older than the minimum.
MINIMUM_DOCKER_COMPOSE_VERSION = "2.29.7"
DOCKER_COMPOSE_INSTALL_VERSION = "5.6.0"
DEVSERVICES_DIR_NAME = "devservices"
CONFIG_FILE_NAME = "config.yml"
DOCKER_CONFIG_DIR = os.environ.get("DOCKER_CONFIG", os.path.expanduser("~/.docker"))
Expand Down
47 changes: 15 additions & 32 deletions devservices/utils/docker_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import subprocess
import time
from typing import NamedTuple
from typing import cast

from packaging import version
from sentry_sdk import logger as sentry_logger
Expand All @@ -19,6 +18,7 @@
from devservices.constants import CONFIG_FILE_NAME
from devservices.constants import DEVSERVICES_DIR_NAME
from devservices.constants import DOCKER_COMPOSE_DOWNLOAD_URL
from devservices.constants import DOCKER_COMPOSE_INSTALL_VERSION
from devservices.constants import DOCKER_USER_PLUGIN_DIR
from devservices.constants import LOGGER_NAME
from devservices.constants import MINIMUM_DOCKER_COMPOSE_VERSION
Expand Down Expand Up @@ -69,22 +69,22 @@ def install_docker_compose() -> None:
else:
raise DockerComposeInstallationError(f"Unsupported operating system: {system}")

url = f"{DOCKER_COMPOSE_DOWNLOAD_URL}/v{MINIMUM_DOCKER_COMPOSE_VERSION}/{binary_name_with_extension}"
url = f"{DOCKER_COMPOSE_DOWNLOAD_URL}/v{DOCKER_COMPOSE_INSTALL_VERSION}/{binary_name_with_extension}"
destination = os.path.join(DOCKER_USER_PLUGIN_DIR, binary_name)
os.makedirs(DOCKER_USER_PLUGIN_DIR, exist_ok=True)

try:
install_binary(
binary_name,
destination,
MINIMUM_DOCKER_COMPOSE_VERSION,
DOCKER_COMPOSE_INSTALL_VERSION,
url,
)
except BinaryInstallError as e:
raise DockerComposeInstallationError(f"Failed to install Docker Compose: {e}")

console.success(
f"Docker Compose {MINIMUM_DOCKER_COMPOSE_VERSION} installed successfully to {destination}"
f"Docker Compose {DOCKER_COMPOSE_INSTALL_VERSION} installed successfully to {destination}"
)

# Verify the installation
Expand Down Expand Up @@ -138,42 +138,25 @@ def check_docker_compose_version() -> None:
# Throw an error if docker daemon isn't running
check_docker_daemon_running()
try:
# Run the docker compose version command
docker_compose_version = get_docker_compose_version()
version_output = get_docker_compose_version()
except DockerComposeError:
docker_compose_version = None
console.warning(
f"Docker Compose is not installed, attempting to install v{MINIMUM_DOCKER_COMPOSE_VERSION}"
f"Docker Compose is not installed, attempting to install v{DOCKER_COMPOSE_INSTALL_VERSION}"
)
install_docker_compose()
return

# Extract the version number from the output
version_output = (
docker_compose_version if docker_compose_version is not None else ""
)

# Use regex to find the version number
pattern = r"^(\d+\.\d+\.\d+)"

match = re.search(pattern, version_output)
if match:
# There is a chance that Any type is returned, so cast this
docker_compose_version = cast(str, match.group(1))
else:
docker_compose_version = None

if docker_compose_version is None:
# Docker Desktop reports versions like "2.29.7-desktop.1", so only match the prefix
match = re.search(r"^(\d+\.\d+\.\d+)", version_output)
if match is None:
console.warning(
f"Unable to detect Docker Compose version, attempting to install v{MINIMUM_DOCKER_COMPOSE_VERSION}"
f"Unable to detect Docker Compose version, attempting to install v{DOCKER_COMPOSE_INSTALL_VERSION}"
)
elif version.parse(docker_compose_version) != version.parse(
MINIMUM_DOCKER_COMPOSE_VERSION
):
elif version.parse(match.group(1)) < version.parse(MINIMUM_DOCKER_COMPOSE_VERSION):
console.warning(
f"Docker Compose version v{docker_compose_version} unsupported, attempting to install v{MINIMUM_DOCKER_COMPOSE_VERSION}"
f"Docker Compose v{match.group(1)} is older than the minimum supported v{MINIMUM_DOCKER_COMPOSE_VERSION}, attempting to install v{DOCKER_COMPOSE_INSTALL_VERSION}"
)
elif version.parse(docker_compose_version) == version.parse(
MINIMUM_DOCKER_COMPOSE_VERSION
):
else:
return
install_docker_compose()

Expand Down
28 changes: 27 additions & 1 deletion tests/utils/test_docker_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,33 @@ def test_check_docker_compose_version_unsupported(
) -> None:
mock_run.return_value.stdout = "2.20.0-desktop.1\n"
check_docker_compose_version()
assert mock_install_docker_compose.is_called()
mock_install_docker_compose.assert_called_once()


@pytest.mark.parametrize(
"installed_version",
["2.29.7", "2.29.7-desktop.1", "2.38.2", "2.40.3-desktop.1", "5.6.0", "6.0.0"],
)
@mock.patch("subprocess.run")
@mock.patch("devservices.utils.docker_compose.install_docker_compose")
def test_check_docker_compose_version_at_or_above_minimum(
mock_install_docker_compose: mock.Mock,
mock_run: mock.Mock,
installed_version: str,
) -> None:
mock_run.return_value.stdout = f"{installed_version}\n"
check_docker_compose_version()
mock_install_docker_compose.assert_not_called()


@mock.patch("subprocess.run")
@mock.patch("devservices.utils.docker_compose.install_docker_compose")
def test_check_docker_compose_version_below_minimum(
mock_install_docker_compose: mock.Mock, mock_run: mock.Mock
) -> None:
mock_run.return_value.stdout = "2.29.6\n"
check_docker_compose_version()
mock_install_docker_compose.assert_called_once()


@mock.patch("subprocess.run")
Expand Down
Loading