|
3 | 3 | import itertools
|
4 | 4 | import logging
|
5 | 5 | import os
|
6 |
| -import sys |
7 | 6 | from typing import List, Dict, Any, Optional
|
8 | 7 | import docker
|
9 | 8 | from docker import DockerClient
|
10 | 9 | from docker.errors import NotFound
|
11 | 10 | from docker.models.containers import Container
|
12 | 11 | from typing import TYPE_CHECKING
|
13 |
| -import dockerpty |
14 | 12 |
|
15 | 13 | from launcher.config import PortPublish
|
16 | 14 | from .image import Image
|
| 15 | +from .pty import exec_command |
17 | 16 |
|
18 | 17 | if TYPE_CHECKING:
|
19 | 18 | from launcher.config import Config
|
@@ -45,28 +44,16 @@ def __init__(self, name: str, image: Image, hostname: str, environment: List[str
|
45 | 44 | self.volumes = volumes
|
46 | 45 | self.ports = ports
|
47 | 46 |
|
48 |
| - def __repr__(self): |
49 |
| - return f"<ContainerSpec {self.name=} {self.image=} {self.hostname=} {self.environment=} {self.command=} {self.volumes=} {self.ports=}>" |
50 | 47 |
|
| 48 | +class OutputStream: |
| 49 | + def __init__(self, fd): |
| 50 | + self.fd = fd |
51 | 51 |
|
52 |
| -class CompareEntity: |
53 |
| - def __init__(self, obj: Any, diff: Any = None): |
54 |
| - self.obj = obj |
55 |
| - self.diff = diff |
56 |
| - |
57 |
| - def __repr__(self): |
58 |
| - return f"<CompareEntity obj={self.obj} diff={self.diff}>" |
59 |
| - |
60 |
| - |
61 |
| -class CompareResult: |
62 |
| - def __init__(self, same: bool, message: str, old: CompareEntity, new: CompareEntity): |
63 |
| - self.same = same |
64 |
| - self.message = message |
65 |
| - self.old = old |
66 |
| - self.new = new |
| 52 | + def isatty(self) -> bool: |
| 53 | + return True |
67 | 54 |
|
68 |
| - def __repr__(self): |
69 |
| - return f"<CompareDetails same={self.same} message={self.message} old={self.old} new={self.new}>" |
| 55 | + def fileno(self) -> int: |
| 56 | + return self.fd |
70 | 57 |
|
71 | 58 |
|
72 | 59 | def diff_details(s1, s2):
|
@@ -267,65 +254,28 @@ def status(self) -> str:
|
267 | 254 | def exec(self, command: str) -> Any:
|
268 | 255 | return self.container.exec_run(command)
|
269 | 256 |
|
270 |
| - def exec2(self, command: str) -> None: |
271 |
| - try: |
272 |
| - dockerpty.exec_command(self.client.api, self.container_name, command) |
273 |
| - except docker.errors.NotFound: |
274 |
| - raise ContainerNotFound(self.name) |
275 |
| - |
276 |
| - def cli(self, command: str) -> None: |
277 |
| - cli_cmd = self._cli + " " + command |
278 |
| - logger.info("[CLI] %s", cli_cmd) |
279 |
| - self.exec2(cli_cmd) |
280 |
| - |
281 |
| - def cli2(self, cmd, shell): |
| 257 | + def cli(self, command: str, exception=False) -> None: |
282 | 258 | if self.mode != "native":
|
283 | 259 | return
|
284 |
| - full_cmd = "%s %s" % (self._cli, cmd) |
285 |
| - self._logger.debug("[Execute] %s", full_cmd) |
286 |
| - _, socket = self._container.exec_run(full_cmd, stdin=True, tty=True, socket=True) |
287 | 260 |
|
288 |
| - shell.redirect_stdin(socket._sock) |
289 | 261 | try:
|
290 |
| - output = "" |
291 |
| - pre_data = None |
292 |
| - while True: |
293 |
| - data = socket.read(1024) |
294 |
| - |
295 |
| - if pre_data is not None: |
296 |
| - data = pre_data + data |
297 |
| - |
298 |
| - if len(data) == 0: |
299 |
| - break |
300 |
| - |
301 |
| - try: |
302 |
| - text = data.decode() |
303 |
| - pre_data = None |
304 |
| - except: |
305 |
| - pre_data = data |
306 |
| - continue |
307 |
| - |
308 |
| - text = self.cli_filter(cmd, text) |
309 |
| - output += text |
310 |
| - |
311 |
| - # Write text in chunks in case trigger BlockingIOError: could not complete without blocking |
312 |
| - # because text is too large to fit the output buffer |
313 |
| - # https://stackoverflow.com/questions/54185874/logging-chokes-on-blockingioerror-write-could-not-complete-without-blocking |
314 |
| - i = 0 |
315 |
| - while i < len(text): |
316 |
| - os.write(sys.stdout.fileno(), text[i: i + 1024].encode()) |
317 |
| - i = i + 1024 |
318 |
| - sys.stdout.flush() |
319 |
| - finally: |
320 |
| - shell.stop_redirect_stdin() |
321 |
| - |
322 |
| - # TODO get exit code here |
323 |
| - exception = self.extract_exception(cmd, output) |
324 |
| - if exception: |
325 |
| - raise exception |
| 262 | + full_cmd = "%s %s" % (self._cli, command) |
| 263 | + # FIXME use blocking docker client here |
| 264 | + output = exec_command(self.client.api, self.container_name, full_cmd) |
| 265 | + logger.debug("[Execute] %s (interactive)\n%s", full_cmd, output) |
| 266 | + try: |
| 267 | + self.extract_exception(command, output) |
| 268 | + except KeyboardInterrupt: |
| 269 | + raise |
| 270 | + except: |
| 271 | + if exception: |
| 272 | + raise |
| 273 | + except docker.errors.NotFound: |
| 274 | + # FIXME use self.container |
| 275 | + raise ContainerNotFound(self.name) |
326 | 276 |
|
327 | 277 | def extract_exception(self, cmd, text):
|
328 |
| - return None |
| 278 | + pass |
329 | 279 |
|
330 | 280 | def cli_filter(self, cmd, text):
|
331 | 281 | return text
|
|
0 commit comments