-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
238 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import re | ||
import asyncio | ||
import logging | ||
from typing import Optional, Tuple, List | ||
from .envvars import COMPOSE_PATH | ||
|
||
|
||
class DockerException(Exception): | ||
"""Raised when reading the docker version. If this succeeds, other errors | ||
will be captured and stored.""" | ||
pass | ||
|
||
|
||
class Docker: | ||
|
||
lock = asyncio.Lock() | ||
|
||
MIN_DOCKER_VERSION = 24 | ||
_RE_DOCKER_VERSION = \ | ||
re.compile(r'Docker version ([0-9]+)\.([0-9]+)\.([0-9]+).*') | ||
|
||
@classmethod | ||
def _read_docker_version(cls, output) -> Optional[Tuple[int, int, int]]: | ||
m = cls._RE_DOCKER_VERSION.match(output) | ||
if not m: | ||
return | ||
try: | ||
major, minor, patch = int(m.group(1)), int(m.group(2)), int(m.group(3)) | ||
except Exception: | ||
return | ||
return major, minor, patch | ||
|
||
@classmethod | ||
def _run(cls, cmd: str) -> Tuple[str, str]: | ||
try: | ||
proc = await asyncio.create_subprocess_shell( | ||
cmd, | ||
stderr=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
cwd=COMPOSE_PATH, | ||
) | ||
stdout, stderr = await proc.communicate() | ||
out = stdout.decode() | ||
err = stderr.decode().strip() | ||
if err: | ||
logging.error(err) | ||
except Exception as e: | ||
err = str(e) or type(e).__name__ | ||
logging.error(f'cmd `{cmd}` failed (err)') | ||
return '', err | ||
else: | ||
return out, err | ||
|
||
@classmethod | ||
async def version(cls) -> str: | ||
async with cls.lock: | ||
out, err = await cls._run('docker -v') | ||
if 'not found' in err or 'not found' in out: | ||
raise DockerException('not found') | ||
if err: | ||
raise Exception(err) | ||
docker_version = cls._read_docker_version(out) | ||
if not docker_version: | ||
raise DockerException('missing docker version') | ||
if docker_version[0] < cls.MIN_DOCKER_VERSION: | ||
vstr = '.'.join([str(i) for i in docker_version]) | ||
raise DockerException(f'docker too old: v{vstr}') | ||
|
||
return docker_version | ||
|
||
@classmethod | ||
async def pull_and_update(cls): | ||
async with cls.lock: | ||
await cls._run('docker compose pull') | ||
await cls._run('docker compose up -d --remove-orphans') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import os | ||
|
||
AGENTCORE_HOST = os.getenv('AGENTCORE_HOST', '127.0.0.1') | ||
AGENTCORE_PORT = int(os.getenv('AGENTCORE_PORT', 8770)) | ||
COMPOSE_FILE = os.getenv('COMPOSE_FILE', '/docker/docker-compose.yml') | ||
ENV_FILE = os.getenv('COMPOSE_FILE', '/docker/.env') | ||
CONFIG_FILE = os.getenv('CONFIG_FILE', '/config/infrasonar.yaml') | ||
COMPOSE_PATH = os.path.dirname(COMPOSE_FILE) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import asyncio | ||
import time | ||
from typing import List, Optional | ||
from .envvars import COMPOSE_PATH | ||
|
||
|
||
class LogView: | ||
|
||
MAX_UNUSED_TIME = 30.0 # kill after 30 seconds unused | ||
|
||
def __init__(self, name: str, on_stop: callable): | ||
self.name = name | ||
self._lines: List[str] = [] | ||
self._process: Optional[asyncio.Process] = None | ||
self._reader: Optional[asyncio.Future] = None | ||
self._watcher: Optional[asyncio.Future] = None | ||
self._on_stop = on_stop | ||
self._accessed: float = 0.0 | ||
|
||
async def start(self, n: Optional[int] = None): | ||
tail = f' -n {n}' if n is not None else '' | ||
cmd = f'docker logs {self.name} -f{tail}' | ||
self._process = await asyncio.create_subprocess_shell( | ||
cmd, | ||
stderr=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
cwd=COMPOSE_PATH) | ||
self._reader = asyncio.ensure_future(self._read()) | ||
self._watcher = asyncio.ensure_future(self._watch()) | ||
await asyncio.sleep(0.5) # give a little time to read some lines | ||
|
||
async def _read(self): | ||
try: | ||
while True: | ||
line = await self.process.stderr.readline() | ||
if line: | ||
try: | ||
line = line.decode() | ||
except Exception as e: | ||
self.lines.append(f'Decoding error: {e}') | ||
else: | ||
self.lines.append(line) | ||
else: | ||
break | ||
except asyncio.CancelledError: | ||
pass | ||
except Exception: | ||
self.stop() | ||
|
||
async def _watch(self): | ||
while True: | ||
now = time.time() | ||
if now - self._accessed > self.MAX_UNUSED_TIME: | ||
break | ||
await asyncio.sleep(1.0) | ||
self.stop() | ||
|
||
def get_lines(self, start: int = 0) -> dict: | ||
self._accessed = time.time() | ||
n = len(self._lines) | ||
if start > n: | ||
start = 0 | ||
return { | ||
'lines': self._lines[start:], | ||
'next': n | ||
} | ||
|
||
def stop(self): | ||
try: | ||
self._reader.cancel() | ||
except Exception: | ||
pass | ||
try: | ||
self._watcher.cancel() | ||
except Exception: | ||
pass | ||
try: | ||
self._process.kill() | ||
|
||
# below is a fix for Python 3.12 (for some reason close is not | ||
# reached on the transport after calling kill or terminatre) | ||
self._process._transport.close() | ||
except Exception: | ||
pass | ||
|
||
self._reader = None | ||
self._watcher = None | ||
self._process = None | ||
|
||
self._on_stop(self.name) | ||
self._on_stop = lambda _: None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters