improve: stream distrobuilder output to build logs in real-time
Added on_output callback to _run_doas_command so build logs update live during the distrobuilder process instead of only after completion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9a0a6b501a
commit
d2791d3db7
|
|
@ -452,18 +452,11 @@ def _build_image_job(build_id, app_name, version, target_server, server_url, rel
|
|||
)
|
||||
cmd = ["doas", "sh", "-c", shell_cmd]
|
||||
|
||||
returncode, output = _run_doas_command(cmd, doas_password, timeout=600)
|
||||
# Stream output to cache in real-time
|
||||
def on_build_output(line):
|
||||
_append_build_log(build_id, line)
|
||||
|
||||
# Parse output, skip password prompt lines
|
||||
output_lines = []
|
||||
for line in output.split("\n"):
|
||||
stripped = line.strip()
|
||||
if not stripped or "password" in stripped.lower():
|
||||
continue
|
||||
output_lines.append(stripped)
|
||||
|
||||
if output_lines:
|
||||
_append_build_log(build_id, *output_lines)
|
||||
returncode, output = _run_doas_command(cmd, doas_password, timeout=600, on_output=on_build_output)
|
||||
|
||||
if returncode != 0:
|
||||
_append_build_log(build_id, "✗ Build failed")
|
||||
|
|
|
|||
|
|
@ -365,8 +365,12 @@ def get_setup_status():
|
|||
return status
|
||||
|
||||
|
||||
def _run_doas_command(cmd, doas_password, timeout=120):
|
||||
"""Run a command with doas using pty.fork() for a real controlling terminal."""
|
||||
def _run_doas_command(cmd, doas_password, timeout=120, on_output=None):
|
||||
"""Run a command with doas using pty.fork() for a real controlling terminal.
|
||||
|
||||
Args:
|
||||
on_output: Optional callback(line: str) called for each line of output in real-time.
|
||||
"""
|
||||
import pty
|
||||
import select
|
||||
|
||||
|
|
@ -379,9 +383,22 @@ def _run_doas_command(cmd, doas_password, timeout=120):
|
|||
|
||||
# Parent process
|
||||
output = b""
|
||||
line_buffer = b""
|
||||
password_sent = False
|
||||
deadline = time.time() + timeout
|
||||
|
||||
def _process_lines(data):
|
||||
"""Process buffered data into lines and call on_output callback."""
|
||||
nonlocal line_buffer
|
||||
if not on_output:
|
||||
return
|
||||
line_buffer += data
|
||||
while b"\n" in line_buffer:
|
||||
line, line_buffer = line_buffer.split(b"\n", 1)
|
||||
text = line.decode("utf-8", errors="replace").strip()
|
||||
if text and "password" not in text.lower():
|
||||
on_output(text)
|
||||
|
||||
try:
|
||||
while time.time() < deadline:
|
||||
ready, _, _ = select.select([master_fd], [], [], 1)
|
||||
|
|
@ -391,6 +408,7 @@ def _run_doas_command(cmd, doas_password, timeout=120):
|
|||
if not chunk:
|
||||
break
|
||||
output += chunk
|
||||
_process_lines(chunk)
|
||||
if not password_sent and b"password" in output.lower():
|
||||
time.sleep(0.1)
|
||||
os.write(master_fd, (doas_password + "\n").encode())
|
||||
|
|
@ -409,15 +427,28 @@ def _run_doas_command(cmd, doas_password, timeout=120):
|
|||
d = os.read(master_fd, 4096)
|
||||
if d:
|
||||
output += d
|
||||
_process_lines(d)
|
||||
else:
|
||||
break
|
||||
except OSError:
|
||||
break
|
||||
else:
|
||||
break
|
||||
# Flush remaining line buffer
|
||||
if on_output and line_buffer:
|
||||
text = line_buffer.decode("utf-8", errors="replace").strip()
|
||||
if text and "password" not in text.lower():
|
||||
on_output(text)
|
||||
line_buffer = b""
|
||||
rc = os.WEXITSTATUS(result[1]) if os.WIFEXITED(result[1]) else 1
|
||||
return rc, output.decode("utf-8", errors="replace")
|
||||
|
||||
# Flush remaining line buffer
|
||||
if on_output and line_buffer:
|
||||
text = line_buffer.decode("utf-8", errors="replace").strip()
|
||||
if text and "password" not in text.lower():
|
||||
on_output(text)
|
||||
|
||||
# Final wait
|
||||
_, status = os.waitpid(pid, 0)
|
||||
rc = os.WEXITSTATUS(status) if os.WIFEXITED(status) else 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue