From f35742fec32fcd4485349dda617ce3e5bcc4cc04 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 8 Jun 2025 03:58:38 +0200 Subject: [PATCH] Update. --- src/snek/service/container.py | 24 ++++++++++++++++- src/snek/system/docker.py | 50 +++++++++++++++++++++++++++++++---- src/snek/view/rpc.py | 18 +++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/snek/service/container.py b/src/snek/service/container.py index 7c0a013..da3355d 100644 --- a/src/snek/service/container.py +++ b/src/snek/service/container.py @@ -9,7 +9,23 @@ class ContainerService(BaseService): super().__init__(*args, **kwargs) self.compose_path = "snek-container-compose.yml" - self.compose = ComposeFileManager(self.compose_path) + self.compose = ComposeFileManager(self.compose_path,self.container_event_handler) + self.event_listeners = {} + + async def add_event_listener(self, name, event): + if not name in self.event_listeners: + self.event_listeners[name] = {} + if not event in self.event_listeners[name]: + self.event_listeners[name][event] = [] + queue = asyncio.Queue() + self.event_listeners[name][event].append(queue) + return queue.get + + async def container_event_handler(self, name, event, data): + event_listeners = self.event_listeners.get(name, {}) + queues = event_listeners.get(event, []) + for queue in queues: + await queue.put(data) async def get_instances(self): return list(self.compose.list_instances()) @@ -20,6 +36,12 @@ class ContainerService(BaseService): async def get(self,channel_uid): return await self.compose.get_instance(await self.get_container_name(channel_uid)) + async def stop(self, channel_uid): + return await self.compose.stop(await self.get_container_name(channel_uid)) + + async def start(self, channel_uid): + return await self.compose.start(await self.get_container_name(channel_uid)) + async def get_status(self, channel_uid): return await self.compose.get_instance_status(await self.get_container_name(channel_uid)) diff --git a/src/snek/system/docker.py b/src/snek/system/docker.py index d37789d..b1b3b5f 100644 --- a/src/snek/system/docker.py +++ b/src/snek/system/docker.py @@ -1,16 +1,15 @@ import copy import yaml - -import yaml -import copy import asyncio import subprocess class ComposeFileManager: - def __init__(self, compose_path="docker-compose.yml"): + def __init__(self, compose_path="docker-compose.yml",event_handler=None): self.compose_path = compose_path self._load() + self.running_instances = {} + self.event_handler = event_handler def _load(self): try: @@ -100,8 +99,47 @@ class ComposeFileManager: stdout, _ = await proc.communicate() running_services = stdout.decode().split() return "running" if name in running_services else "stopped" - # Storage size is not tracked in compose files; would need Docker API for that. + async def stop(self, name): + """Asynchronously stop a container by doing 'docker compose stop [name]'.""" + if name not in self.list_instances(): + return False + status = await self.get_instance_status(name) + if status != "running": + return True + + proc = await asyncio.create_subprocess_exec( + "docker", "compose", "-f", self.compose_path, "stop", name, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + if name in self.running_instances: + del self.running_instances[name] + stdout, stderr = await proc.communicate() + if proc.returncode != 0: + raise RuntimeError(f"Failed to stop {name}: {stderr.decode()}") + return stdout.decode() + + async def start(self, name): + """Asynchronously start a container by doing 'docker compose up -d [name]'.""" + if name not in self.list_instances(): + return False + + status = await self.get_instance_status(name) + if name in self.running_instances and status == "running": + return True + else: + del self.running_instances[name] + + proc = await asyncio.create_subprocess_exec( + "docker", "compose", "-f", self.compose_path, "up", "-d", name, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + if proc.returncode != 0: + raise RuntimeError(f"Failed to start {name}: {stderr.decode()}") + return stdout.decode() # Example usage: # mgr = ComposeFileManager() @@ -109,3 +147,5 @@ class ComposeFileManager: # print(mgr.list_instances()) # mgr.duplicate_instance('web', 'web_copy') # mgr.remove_instance('web_copy') +# await mgr.start('web') + diff --git a/src/snek/view/rpc.py b/src/snek/view/rpc.py index 08aae79..10d1197 100644 --- a/src/snek/view/rpc.py +++ b/src/snek/view/rpc.py @@ -223,6 +223,24 @@ class RPCView(BaseView): } return result + async def start_container(self, channel_uid): + self._require_login() + channel_member = await self.services.channel_member.get( + channel_uid=channel_uid, user_uid=self.user_uid + ) + if not channel_member: + raise Exception("Not allowed") + return await self.services.container.start(channel_uid) + + async def stop_container(self, channel_uid): + self._require_login() + channel_member = await self.services.channel_member.get( + channel_uid=channel_uid, user_uid=self.user_uid + ) + if not channel_member: + raise Exception("Not allowed") + return await self.services.container.stop(channel_uid) + async def get_container_status(self, channel_uid): self._require_login() channel_member = await self.services.channel_member.get(