clan-cli: cmd.py uses pseudo terminal now. Remove tty.py
Some checks failed
buildbot/nix-build .#checks.x86_64-linux.clan-dep-age Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-bash Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-fakeroot Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-e2fsprogs Build done.
buildbot/nix-build .#checks.x86_64-linux.check-for-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-git Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-nix Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-ts-api Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli-docs Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.borgbackup Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-docs Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.renderClanOptions Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-qemu" Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-rsync Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sops Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sshpass Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-tor Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-vm-manager-no-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-openssh Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-default Build done.
buildbot/nix-build .#checks.x86_64-linux.container Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.matrix-synapse Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-apk Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-archlinux Build done.
buildbot/nix-build .#checks.x86_64-linux.treefmt Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-rpm Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-vm-manager-pytest Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotier-members Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-mypy" Build done.
buildbot/nix-build .#checks.x86_64-linux.package-default Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-zbar Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-example-valid Build done.
buildbot/nix-build .#checks.x86_64-linux.deltachat Build done.
buildbot/nix-build .#checks.x86_64-linux.package-editor Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-deb Build done.
buildbot/nix-build .#checks.x86_64-linux.package-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-impure-checks Build done.
buildbot/nix-build .#checks.x86_64-linux.module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-merge-after-ci Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.package-moonlight-sunshine-accept Build done.
buildbot/nix-build .#checks.x86_64-linux.package-pending-reviews Build done.
buildbot/nix-build .#checks.x86_64-linux.package-tea-create-pr Build done.
buildbot/nix-build .#checks.x86_64-linux.package-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotierone Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-nix-unit-tests Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-vm-manager Build done.
buildbot/nix-build .#checks.x86_64-linux.package-deploy-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-function-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.secrets Build done.
buildbot/nix-build .#checks.x86_64-linux.package-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-with-core Build done.
buildbot/nix-build .#checks.x86_64-linux.wayland-proxy-virtwl Build done.
buildbot/nix-build .#checks.x86_64-linux.zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.test-backups Build done.
buildbot/nix-build .#checks.x86_64-linux.flash Build done.
buildbot/nix-build .#checks.x86_64-linux.syncthing Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-vm-manager Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-without-core Build done.
buildbot/nix-build .#checks.x86_64-linux.test-installation Build done.
buildbot/nix-eval Build done.
checks / checks-impure (pull_request) Has been cancelled

This commit is contained in:
Luis Hebendanz 2024-06-02 16:30:22 +02:00
parent dff07c1420
commit 48ce996ca5
4 changed files with 87 additions and 88 deletions

View File

@ -1,5 +1,6 @@
import logging import logging
import os import os
import pty
import select import select
import shlex import shlex
import subprocess import subprocess
@ -8,7 +9,6 @@ import weakref
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import IO, Any
from .custom_logger import get_caller from .custom_logger import get_caller
from .errors import ClanCmdError, CmdOut from .errors import ClanCmdError, CmdOut
@ -23,42 +23,6 @@ class Log(Enum):
NONE = 4 NONE = 4
def handle_output(process: subprocess.Popen, log: Log) -> tuple[str, str]:
rlist = [process.stdout, process.stderr]
stdout_buf = b""
stderr_buf = b""
while len(rlist) != 0:
r, _, _ = select.select(rlist, [], [], 0.1)
if len(r) == 0: # timeout in select
if process.poll() is None:
continue
# Process has exited
break
def handle_fd(fd: IO[Any] | None) -> bytes:
if fd and fd in r:
read = os.read(fd.fileno(), 4096)
if len(read) != 0:
return read
rlist.remove(fd)
return b""
ret = handle_fd(process.stdout)
if ret and log in [Log.STDOUT, Log.BOTH]:
sys.stdout.buffer.write(ret)
sys.stdout.flush()
stdout_buf += ret
ret = handle_fd(process.stderr)
if ret and log in [Log.STDERR, Log.BOTH]:
sys.stderr.buffer.write(ret)
sys.stderr.flush()
stderr_buf += ret
return stdout_buf.decode("utf-8", "replace"), stderr_buf.decode("utf-8", "replace")
class TimeTable: class TimeTable:
""" """
This class is used to store the time taken by each command This class is used to store the time taken by each command
@ -114,38 +78,91 @@ def run(
) )
else: else:
glog.debug(f"$: {shlex.join(cmd)} \nCaller: {get_caller()}") glog.debug(f"$: {shlex.join(cmd)} \nCaller: {get_caller()}")
# Create pseudo-terminals for stdout/stderr and stdin
stdout_master_fd, stdout_slave_fd = pty.openpty()
stderr_master_fd, stderr_slave_fd = pty.openpty()
tstart = datetime.now() tstart = datetime.now()
# Start the subprocess proc = subprocess.Popen(
process = subprocess.Popen(
cmd, cmd,
cwd=str(cwd), preexec_fn=os.setsid,
stdin=stdout_slave_fd,
stdout=stdout_slave_fd,
stderr=stderr_slave_fd,
close_fds=True,
env=env, env=env,
stdout=subprocess.PIPE, cwd=str(cwd),
stderr=subprocess.PIPE,
) )
stdout_buf, stderr_buf = handle_output(process, log)
os.close(stdout_slave_fd) # Close slave FD in parent
os.close(stderr_slave_fd) # Close slave FD in parent
stdout_file = sys.stdout
stderr_file = sys.stderr
stdout_buf = b""
stderr_buf = b""
if input: if input:
process.communicate(input) written_b = os.write(stdout_master_fd, input)
else:
process.wait()
tend = datetime.now()
if written_b != len(input):
raise ValueError("Could not write all input to subprocess")
rlist = [stdout_master_fd, stderr_master_fd]
def handle_fd(fd: int | None) -> bytes:
if fd and fd in r:
try:
read = os.read(fd, 4096)
if len(read) != 0:
return read
except OSError:
pass
rlist.remove(fd)
return b""
while len(rlist) != 0:
r, w, e = select.select(rlist, [], [], 0.1)
if len(r) == 0: # timeout in select
if proc.poll() is None:
continue
# Process has exited
break
ret = handle_fd(stdout_master_fd)
stdout_buf += ret
if ret and log in [Log.STDOUT, Log.BOTH]:
stdout_file.buffer.write(ret)
stdout_file.flush()
ret = handle_fd(stderr_master_fd)
stderr_buf += ret
if ret and log in [Log.STDERR, Log.BOTH]:
stderr_file.buffer.write(ret)
stderr_file.flush()
os.close(stdout_master_fd)
os.close(stderr_master_fd)
proc.wait()
tend = datetime.now()
global TIME_TABLE global TIME_TABLE
TIME_TABLE.add(shlex.join(cmd), tend - tstart) TIME_TABLE.add(shlex.join(cmd), tend - tstart)
# Wait for the subprocess to finish # Wait for the subprocess to finish
cmd_out = CmdOut( cmd_out = CmdOut(
stdout=stdout_buf, stdout=stdout_buf.decode("utf-8", "replace"),
stderr=stderr_buf, stderr=stderr_buf.decode("utf-8", "replace"),
cwd=cwd, cwd=cwd,
command=shlex.join(cmd), command=shlex.join(cmd),
returncode=process.returncode, returncode=proc.returncode,
msg=error_msg, msg=error_msg,
) )
if check and process.returncode != 0: if check and proc.returncode != 0:
raise ClanCmdError(cmd_out) raise ClanCmdError(cmd_out)
return cmd_out return cmd_out

View File

@ -1,5 +1,6 @@
import argparse import argparse
import getpass import getpass
import logging
import os import os
import shutil import shutil
import sys import sys
@ -8,7 +9,6 @@ from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import IO from typing import IO
from .. import tty
from ..errors import ClanError from ..errors import ClanError
from ..git import commit_files from ..git import commit_files
from .folders import ( from .folders import (
@ -21,6 +21,13 @@ from .folders import (
from .sops import decrypt_file, encrypt_file, ensure_sops_key, read_key, update_keys from .sops import decrypt_file, encrypt_file, ensure_sops_key, read_key, update_keys
from .types import VALID_SECRET_NAME, secret_name_type from .types import VALID_SECRET_NAME, secret_name_type
log = logging.getLogger(__name__)
def tty_is_interactive() -> bool:
"""Returns true if the current process is interactive"""
return sys.stdin.isatty() and sys.stdout.isatty()
def update_secrets( def update_secrets(
flake_dir: Path, filter_secrets: Callable[[Path], bool] = lambda _: True flake_dir: Path, filter_secrets: Callable[[Path], bool] = lambda _: True
@ -49,11 +56,11 @@ def collect_keys_for_type(folder: Path) -> set[str]:
try: try:
target = p.resolve() target = p.resolve()
except FileNotFoundError: except FileNotFoundError:
tty.warn(f"Ignoring broken symlink {p}") log.warn(f"Ignoring broken symlink {p}")
continue continue
kind = target.parent.name kind = target.parent.name
if folder.name != kind: if folder.name != kind:
tty.warn(f"Expected {p} to point to {folder} but points to {target.parent}") log.warn(f"Expected {p} to point to {folder} but points to {target.parent}")
continue continue
keys.add(read_key(target)) keys.add(read_key(target))
return keys return keys
@ -285,7 +292,7 @@ def set_command(args: argparse.Namespace) -> None:
secret_value = None secret_value = None
elif env_value: elif env_value:
secret_value = env_value secret_value = env_value
elif tty.is_interactive(): elif tty_is_interactive():
secret_value = getpass.getpass(prompt="Paste your secret: ") secret_value = getpass.getpass(prompt="Paste your secret: ")
encrypt_secret( encrypt_secret(
Path(args.flake), Path(args.flake),

View File

@ -24,13 +24,14 @@ class SopsKey:
def get_public_key(privkey: str) -> str: def get_public_key(privkey: str) -> str:
cmd = nix_shell(["nixpkgs#age"], ["age-keygen", "-y"]) cmd = nix_shell(["nixpkgs#age"], ["age-keygen", "-y"])
try:
res = run( res = subprocess.run(
cmd, cmd, input=privkey, stdout=subprocess.PIPE, text=True, check=True
input=privkey.encode("utf-8"), )
error_msg="Failed to get public key for age private key. Is the key malformed?", except subprocess.CalledProcessError as e:
) raise ClanError(
"Failed to get public key for age private key. Is the key malformed?"
) from e
return res.stdout.strip() return res.stdout.strip()

View File

@ -1,26 +0,0 @@
import sys
from collections.abc import Callable
from typing import IO, Any
def is_interactive() -> bool:
"""Returns true if the current process is interactive"""
return sys.stdin.isatty() and sys.stdout.isatty()
def color_text(code: int, file: IO[Any] = sys.stdout) -> Callable[[str], None]:
"""
Print with color if stderr is a tty
"""
def wrapper(text: str) -> None:
if file.isatty():
print(f"\x1b[{code}m{text}\x1b[0m", file=file)
else:
print(text, file=file)
return wrapper
warn = color_text(91, file=sys.stderr)
info = color_text(92, file=sys.stderr)