mirror of
ssh://git.janware.com/janware/proj/jw-pkg
synced 2026-04-24 17:23:36 +02:00
jw.pkg.*.run_xxx(): Return exit status
Most run_xxx() return stdout and stderr. There's no way, really, for the caller to get hold of the exit code of the spawned executable. It can pass throw=true, catch, and assume a non-zero exit status. But that's not semantically clean, since the spawned function can well be a test function which is expected to return a non-zero status code, and the caller might be interested in what code that was, exactly. The clearest way to solve this is to return the exit code as well. This commit does that. Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
3312b53a5b
commit
565946643b
6 changed files with 25 additions and 25 deletions
|
|
@ -24,7 +24,7 @@ async def all_installed_packages() -> Iterable[Package]: # export
|
||||||
opts.append('--queryformat')
|
opts.append('--queryformat')
|
||||||
tag_str = '|'.join([f'%{{{tag}}}' for tag in query_tags]) + r'\n'
|
tag_str = '|'.join([f'%{{{tag}}}' for tag in query_tags]) + r'\n'
|
||||||
opts.append(tag_str)
|
opts.append(tag_str)
|
||||||
package_list_str, stderr = await run_rpm(['-qa', *opts], sudo=False)
|
package_list_str, stderr, status = await run_rpm(['-qa', *opts], sudo=False)
|
||||||
for package_str in package_list_str.splitlines():
|
for package_str in package_list_str.splitlines():
|
||||||
tags = package_str.split('|')
|
tags = package_str.split('|')
|
||||||
ret.append(Package(name=tags[0], vendor=tags[1], packager=tags[2], url=tags[3]))
|
ret.append(Package(name=tags[0], vendor=tags[1], packager=tags[2], url=tags[3]))
|
||||||
|
|
@ -32,5 +32,5 @@ async def all_installed_packages() -> Iterable[Package]: # export
|
||||||
|
|
||||||
async def list_files(pkg: str) -> list[str]: # export
|
async def list_files(pkg: str) -> list[str]: # export
|
||||||
opts: list[str] = []
|
opts: list[str] = []
|
||||||
file_list_str, stderr = await run_rpm(['-ql', pkg, *opts], sudo=False)
|
file_list_str, stderr, status = await run_rpm(['-ql', pkg, *opts], sudo=False)
|
||||||
return file_list_str.splitlines()
|
return file_list_str.splitlines()
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ class CmdBuild(Cmd): # export
|
||||||
env = await get_profile_env(keep=keep)
|
env = await get_profile_env(keep=keep)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stdout, stderr = await run_cmd(
|
stdout, stderr, status = await run_cmd(
|
||||||
make_cmd,
|
make_cmd,
|
||||||
wd=wd,
|
wd=wd,
|
||||||
throw=True,
|
throw=True,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class CmdGetAuthInfo(Cmd): # export
|
||||||
if not os.path.isdir(jw_pkg_dir + '/.git'):
|
if not os.path.isdir(jw_pkg_dir + '/.git'):
|
||||||
log(DEBUG, f'jw-pkg directory is not a Git repo: {jw_pkg_dir}')
|
log(DEBUG, f'jw-pkg directory is not a Git repo: {jw_pkg_dir}')
|
||||||
return
|
return
|
||||||
remotes, stderr = await run_cmd(['git', '-C', jw_pkg_dir, 'remote', '-v'])
|
remotes, stderr, status = await run_cmd(['git', '-C', jw_pkg_dir, 'remote', '-v'])
|
||||||
result: dict[str, str] = {}
|
result: dict[str, str] = {}
|
||||||
for line in remotes.splitlines():
|
for line in remotes.splitlines():
|
||||||
name, url, typ = re.split(r'\s+', line)
|
name, url, typ = re.split(r'\s+', line)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class CmdListRepos(Cmd): # export
|
||||||
cmd_input = (f'-u {username}:{password}').encode('utf-8')
|
cmd_input = (f'-u {username}:{password}').encode('utf-8')
|
||||||
curl_args.extend(['-K-'])
|
curl_args.extend(['-K-'])
|
||||||
curl_args.append(f'https://api.github.com/users/{args.from_owner}/repos')
|
curl_args.append(f'https://api.github.com/users/{args.from_owner}/repos')
|
||||||
repos = await run_curl(curl_args, cmd_input=cmd_input)
|
repos, stderr, status = await run_curl(curl_args, cmd_input=cmd_input)
|
||||||
for repo in repos:
|
for repo in repos:
|
||||||
print(repo['name'])
|
print(repo['name'])
|
||||||
return
|
return
|
||||||
|
|
@ -71,7 +71,7 @@ class CmdListRepos(Cmd): # export
|
||||||
api_url = f'{args.base_url}/api/v1/{entities_dir}/{args.from_owner}/repos'
|
api_url = f'{args.base_url}/api/v1/{entities_dir}/{args.from_owner}/repos'
|
||||||
try:
|
try:
|
||||||
tried.append(api_url)
|
tried.append(api_url)
|
||||||
repos = await run_curl(curl_args + [api_url], cmd_input=cmd_input)
|
repos, stderr, status = await run_curl(curl_args + [api_url], cmd_input=cmd_input)
|
||||||
for repo in repos:
|
for repo in repos:
|
||||||
print(repo['name'])
|
print(repo['name'])
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -97,5 +97,5 @@ class SSHClientCmd(SSHClient): # export
|
||||||
self.__init_askpass()
|
self.__init_askpass()
|
||||||
cmd_arr = ['ssh']
|
cmd_arr = ['ssh']
|
||||||
cmd_arr.append(self.hostname)
|
cmd_arr.append(self.hostname)
|
||||||
stdout, stderr = await run_cmd(['ssh', self.hostname, cmd])
|
stdout, stderr, status = await run_cmd(['ssh', self.hostname, cmd])
|
||||||
return stdout
|
return stdout
|
||||||
|
|
|
||||||
|
|
@ -76,13 +76,13 @@ async def run_cmd(
|
||||||
|
|
||||||
def __make_pty_reader(collector: list[bytes], enc_for_verbose: str):
|
def __make_pty_reader(collector: list[bytes], enc_for_verbose: str):
|
||||||
def _read(fd):
|
def _read(fd):
|
||||||
data = os.read(fd, 1024)
|
ret = os.read(fd, 1024)
|
||||||
if not data:
|
if not ret:
|
||||||
return data
|
return ret
|
||||||
collector.append(data)
|
collector.append(ret)
|
||||||
if verbose:
|
if verbose:
|
||||||
__log(NOTICE, data.decode(enc_for_verbose, errors="replace").rstrip("\n"))
|
__log(NOTICE, ret.decode(enc_for_verbose, errors="replace").rstrip("\n"))
|
||||||
return data
|
return ret
|
||||||
return _read
|
return _read
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
|
|
@ -128,16 +128,17 @@ async def run_cmd(
|
||||||
os.environ.update(old_env)
|
os.environ.update(old_env)
|
||||||
return pty.spawn(args, master_read=reader)
|
return pty.spawn(args, master_read=reader)
|
||||||
|
|
||||||
__check_exit_code(await asyncio.to_thread(_spawn))
|
exit_code = await asyncio.to_thread(_spawn)
|
||||||
|
__check_exit_code(exit_code)
|
||||||
|
|
||||||
# PTY merges stdout/stderr
|
# PTY merges stdout/stderr
|
||||||
stdout_b = b"".join(stdout_chunks_b) if stdout_chunks_b else None
|
stdout_b = b"".join(stdout_chunks_b) if stdout_chunks_b else None
|
||||||
if want_bytes:
|
if want_bytes:
|
||||||
return stdout_b, None
|
return stdout_b, None, exit_code
|
||||||
|
|
||||||
stdout_dec_enc = (sys.stdout.encoding or "utf-8") if output_encoding is None else output_encoding
|
stdout_dec_enc = (sys.stdout.encoding or "utf-8") if output_encoding is None else output_encoding
|
||||||
stdout_s = stdout_b.decode(stdout_dec_enc, errors="replace") if stdout_b is not None else None
|
stdout_s = stdout_b.decode(stdout_dec_enc, errors="replace") if stdout_b is not None else None
|
||||||
return stdout_s, None
|
return stdout_s, None, exit_code
|
||||||
|
|
||||||
# -- non-interactive mode
|
# -- non-interactive mode
|
||||||
stdin = (
|
stdin = (
|
||||||
|
|
@ -197,7 +198,7 @@ async def run_cmd(
|
||||||
stderr_b = b"".join(stderr_parts_b) if stderr_parts_b else None
|
stderr_b = b"".join(stderr_parts_b) if stderr_parts_b else None
|
||||||
|
|
||||||
if want_bytes:
|
if want_bytes:
|
||||||
return stdout_b, stderr_b
|
return stdout_b, stderr_b, exit_code
|
||||||
|
|
||||||
if output_encoding is None:
|
if output_encoding is None:
|
||||||
stdout_dec_enc = sys.stdout.encoding or "utf-8"
|
stdout_dec_enc = sys.stdout.encoding or "utf-8"
|
||||||
|
|
@ -212,7 +213,7 @@ async def run_cmd(
|
||||||
if not want_bytes:
|
if not want_bytes:
|
||||||
__check_exit_code(exit_code, stdout=stdout_s, stderr=stderr_s)
|
__check_exit_code(exit_code, stdout=stdout_s, stderr=stderr_s)
|
||||||
|
|
||||||
return stdout_s, stderr_s
|
return stdout_s, stderr_s, exit_code
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if cwd is not None:
|
if cwd is not None:
|
||||||
|
|
@ -225,10 +226,10 @@ async def run_curl(args: list[str], parse_json: bool=True, wd=None, throw=None,
|
||||||
if not verbose:
|
if not verbose:
|
||||||
cmd.append('-s')
|
cmd.append('-s')
|
||||||
cmd.extend(args)
|
cmd.extend(args)
|
||||||
ret, stderr = await run_cmd(cmd, wd=wd, throw=throw, verbose=verbose, cmd_input=cmd_input)
|
ret, stderr, status = await run_cmd(cmd, wd=wd, throw=throw, verbose=verbose, cmd_input=cmd_input)
|
||||||
if parse_json:
|
if parse_json:
|
||||||
try:
|
try:
|
||||||
return json.loads(ret)
|
ret = json.loads(ret)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
size = 'unknown number of'
|
size = 'unknown number of'
|
||||||
try:
|
try:
|
||||||
|
|
@ -238,7 +239,7 @@ async def run_curl(args: list[str], parse_json: bool=True, wd=None, throw=None,
|
||||||
print(f'Failed to parse {size} bytes output of command '
|
print(f'Failed to parse {size} bytes output of command '
|
||||||
+ f'>{pretty_cmd(cmd, wd)}< ({str(e)}): "{ret}"', file=sys.stderr)
|
+ f'>{pretty_cmd(cmd, wd)}< ({str(e)}): "{ret}"', file=sys.stderr)
|
||||||
raise
|
raise
|
||||||
return ret
|
return ret, stderr, status
|
||||||
|
|
||||||
async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=None):
|
async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=None):
|
||||||
assert host is None # Currently unsupported
|
assert host is None # Currently unsupported
|
||||||
|
|
@ -260,7 +261,7 @@ async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=No
|
||||||
continue # Can't get user name from SSH_ASKPASS
|
continue # Can't get user name from SSH_ASKPASS
|
||||||
case AskpassKey.Password:
|
case AskpassKey.Password:
|
||||||
exe_arg += 'Password'
|
exe_arg += 'Password'
|
||||||
ret, stderr = await run_cmd([exe, exe_arg], throw=False)
|
ret, stderr, status = await run_cmd([exe, exe_arg], throw=False)
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
return ret
|
return ret
|
||||||
return None
|
return None
|
||||||
|
|
@ -280,8 +281,7 @@ async def run_sudo(cmd: list[str], mod_env: dict[str, str] = {}, opts: list[str]
|
||||||
cmdline.extend(cmd)
|
cmdline.extend(cmd)
|
||||||
if interactive:
|
if interactive:
|
||||||
cmd_input = "mode:interactive"
|
cmd_input = "mode:interactive"
|
||||||
stdout, stderr = await run_cmd(cmdline, throw=True, verbose=verbose, env=env, cmd_input=cmd_input)
|
return await run_cmd(cmdline, throw=True, verbose=verbose, env=env, cmd_input=cmd_input)
|
||||||
return stdout, stderr
|
|
||||||
|
|
||||||
async def get_username(args: Namespace|None=None, url: str|None=None, askpass_env: list[str]=[]) -> str: # export
|
async def get_username(args: Namespace|None=None, url: str|None=None, askpass_env: list[str]=[]) -> str: # export
|
||||||
url_user = None if url is None else urlparse(url).username
|
url_user = None if url is None else urlparse(url).username
|
||||||
|
|
@ -329,7 +329,7 @@ async def get_profile_env(throw: bool=True, keep: Iterable[str]|bool=False) -> d
|
||||||
}
|
}
|
||||||
# Run bash as a login shell, which sources /etc/profile, then print environment as NUL-separated key=value pairs
|
# Run bash as a login shell, which sources /etc/profile, then print environment as NUL-separated key=value pairs
|
||||||
cmd = ['/usr/bin/env', '-i', '/bin/bash', '-lc', 'env -0']
|
cmd = ['/usr/bin/env', '-i', '/bin/bash', '-lc', 'env -0']
|
||||||
stdout, stderr = await run_cmd(cmd, throw=throw, output_encoding="bytes", verbose=True, env=env)
|
stdout, stderr, status = await run_cmd(cmd, throw=throw, output_encoding="bytes", verbose=True, env=env)
|
||||||
ret: dict[str, str] = {}
|
ret: dict[str, str] = {}
|
||||||
for entry in stdout.split(b"\0"):
|
for entry in stdout.split(b"\0"):
|
||||||
if not entry:
|
if not entry:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue