Merge pull request 'feat: Python on whales & embedded docker CLI' (#29) from compose into develop (resolves #23)

Reviewed-on: https://hattori.ztsh.eu/iac/karl/pulls/29
This commit is contained in:
Piotr Dec 2026-02-11 21:29:59 +01:00
commit b10fcf702a
3 changed files with 12 additions and 50 deletions

View file

@ -14,10 +14,10 @@ ADD . /app
RUN --mount=type=cache,target=/root/.cache/uv \ RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked uv sync --locked
FROM python:3.12-alpine3.22 FROM docker:29.1.3-cli
ENV USER=karl ENV USER=karl
ENV GROUPNAME=docker ENV GROUPNAME=docker-host
ENV UID=1000 ENV UID=1000
ENV GID=994 ENV GID=994
ENV PYTHONPATH="/app" ENV PYTHONPATH="/app"
@ -39,7 +39,8 @@ RUN addgroup \
$USER $USER
RUN apk update --no-cache \ 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/.venv /app/.venv
COPY --from=builder --chown=app:app /app/src /app/src COPY --from=builder --chown=app:app /app/src /app/src

View file

@ -14,13 +14,13 @@ dependencies = [
"pyyaml>=6.0.2", "pyyaml>=6.0.2",
"gitpython>=3.1.45", "gitpython>=3.1.45",
"pykeepass>=4.1.1.post1", "pykeepass>=4.1.1.post1",
"docker>=7.1.0",
"injectable==4.0.1", "injectable==4.0.1",
"py-automapper>=2.2.0", "py-automapper>=2.2.0",
"fastapi-utils>=0.8.0", "fastapi-utils>=0.8.0",
"keyring>=25.6.0", "keyring>=25.6.0",
"keyring-backend>=0.1.0", "keyring-backend>=0.1.0",
"bubus>=1.5.6", "bubus>=1.5.6",
"python-on-whales>=0.79.0",
] ]
[dependency-groups] [dependency-groups]

View file

@ -1,11 +1,9 @@
import logging import logging
import os
from pathlib import Path from pathlib import Path
import docker
from docker.models.containers import Container
from injectable import injectable from injectable import injectable
from python_on_whales import DockerClient
from karl.model.containers import Tree, Compose, SimpleContainer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -13,47 +11,10 @@ logger = logging.getLogger(__name__)
@injectable(singleton=True) @injectable(singleton=True)
class DockerService: class DockerService:
def __init__(self): def __init__(self):
self._client = docker.from_env() self._client = DockerClient()
# 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
def reload(self, compose_path: Path): def reload(self, compose_path: Path):
# TODO: Won't work in docker container os.chdir(compose_path.parent)
cmd = ["sudo", "docker", "compose", "-f", str(compose_path), "up", "-d"] self._client.compose.ps()
import subprocess self._client.compose.down(remove_orphans=True)
try: self._client.compose.up()
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