diff --git a/Dockerfile b/Dockerfile index 4dfb251..f6f779b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,10 @@ ADD . /app RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked -FROM python:3.12-alpine3.22 +FROM docker:29.1.3-cli ENV USER=karl -ENV GROUPNAME=docker +ENV GROUPNAME=docker-host ENV UID=1000 ENV GID=994 ENV PYTHONPATH="/app" @@ -39,7 +39,8 @@ RUN addgroup \ $USER RUN apk update --no-cache \ - && apk add --no-cache git + && apk add --no-cache git python3 \ + && ln -s /usr/bin/python3 /usr/local/bin/python3 COPY --from=builder --chown=app:app /app/.venv /app/.venv COPY --from=builder --chown=app:app /app/src /app/src diff --git a/pyproject.toml b/pyproject.toml index ddbc958..b866a13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,13 +14,13 @@ dependencies = [ "pyyaml>=6.0.2", "gitpython>=3.1.45", "pykeepass>=4.1.1.post1", - "docker>=7.1.0", "injectable==4.0.1", "py-automapper>=2.2.0", "fastapi-utils>=0.8.0", "keyring>=25.6.0", "keyring-backend>=0.1.0", "bubus>=1.5.6", + "python-on-whales>=0.79.0", ] [dependency-groups] diff --git a/src/karl/services/containers.py b/src/karl/services/containers.py index 3ec794c..cc7cf56 100644 --- a/src/karl/services/containers.py +++ b/src/karl/services/containers.py @@ -1,11 +1,9 @@ import logging +import os from pathlib import Path -import docker -from docker.models.containers import Container from injectable import injectable - -from karl.model.containers import Tree, Compose, SimpleContainer +from python_on_whales import DockerClient logger = logging.getLogger(__name__) @@ -13,47 +11,10 @@ logger = logging.getLogger(__name__) @injectable(singleton=True) class DockerService: def __init__(self): - self._client = docker.from_env() - # logger.info(f"Docker client initialized. Plugins: {self._client.plugins()}") - self._tree = self._init_tree() - - def _init_tree(self) -> Tree: - tree = Tree() - container: Container - for container in self._client.containers.list(): - labels = container.labels - working_dir = labels.get("com.docker.compose.project.working_dir") - if working_dir: - if tree.composes.get(working_dir) is None: - tree.composes[working_dir] = Compose(working_dir) - tree.composes[working_dir].containers.append(SimpleContainer.from_container(container)) - else: - tree.containers.append(SimpleContainer.from_container(container)) - return tree - - @property - def tree(self) -> Tree: - return self._tree + self._client = DockerClient() def reload(self, compose_path: Path): - # TODO: Won't work in docker container - cmd = ["sudo", "docker", "compose", "-f", str(compose_path), "up", "-d"] - import subprocess - try: - process = subprocess.run( - cmd, - capture_output=True, - text=True, - check=False - ) - if process.returncode != 0: - logger.error(f"Docker compose failed with code {process.returncode}") - logger.error(f"stderr: {process.stderr}") - raise Exception(f"Docker compose failed: {process.stderr}") - - logger.info(f"Docker compose executed successfully") - logger.debug(f"stdout: {process.stdout}") - return process.stdout, process.stderr, process.returncode - except Exception as e: - logger.error(f"Failed to execute docker compose command: {e}") - raise e + os.chdir(compose_path.parent) + self._client.compose.ps() + self._client.compose.down(remove_orphans=True) + self._client.compose.up()