lib.ExecContext.run(), .sudo(): Rename env
The name of the env parameter to ExecContext.run() and .sudo() is not descriptive enough for which environment is supposed to be modified and how, so rename and split it up as follows:
- .run(): env -> mod_env
- .sudo(): env -> mod_env_sudo and mod_env_cmd
The parameters have the following meaning:
- "mod_env*" means that the environment is modified, not replaced
- "mod_env" and "mod_env_cmd" modify the environment "cmd" runs in
- "mod_env_sudo" modifies the environment sudo runs in
Fix the fallout of the API change all over jw-pkg.
Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
9f756222fe
commit
54aecff8e4
9 changed files with 164 additions and 159 deletions
|
|
@ -25,7 +25,7 @@ class Local(Base):
|
|||
wd: str|None,
|
||||
verbose: bool,
|
||||
cmd_input: bytes|None,
|
||||
env: dict[str, str]|None,
|
||||
mod_env: dict[str, str]|None,
|
||||
interactive: bool,
|
||||
log_prefix: str
|
||||
) -> Result:
|
||||
|
|
@ -58,10 +58,10 @@ class Local(Base):
|
|||
|
||||
def _spawn():
|
||||
# Apply env in PTY mode by temporarily updating os.environ around spawn.
|
||||
if env:
|
||||
if mod_env:
|
||||
old_env = os.environ.copy()
|
||||
try:
|
||||
os.environ.update(env)
|
||||
os.environ.update(mod_env)
|
||||
return pty.spawn(cmd, master_read=reader)
|
||||
finally:
|
||||
os.environ.clear()
|
||||
|
|
@ -81,17 +81,17 @@ class Local(Base):
|
|||
# -- non-interactive mode
|
||||
stdin = asyncio.subprocess.DEVNULL if cmd_input is None else asyncio.subprocess.PIPE
|
||||
|
||||
if env:
|
||||
if mod_env:
|
||||
new_env = os.environ.copy()
|
||||
new_env.update(env)
|
||||
env = new_env
|
||||
new_env.update(mod_env)
|
||||
mod_env = new_env
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdin=stdin,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=env,
|
||||
env=mod_env,
|
||||
)
|
||||
|
||||
stdout_parts: list[bytes] = []
|
||||
|
|
@ -143,21 +143,6 @@ class Local(Base):
|
|||
if cwd is not None:
|
||||
os.chdir(cwd)
|
||||
|
||||
async def _sudo(self, cmd: list[str], mod_env: dict[str, str], opts: list[str], *args, **kwargs) -> Result:
|
||||
env: dict[str, str]|None = None
|
||||
cmd_input: bytes|None = None
|
||||
if mod_env:
|
||||
env = os.environ.copy()
|
||||
env.update(mod_env)
|
||||
cmdline = []
|
||||
if os.getuid() != 0:
|
||||
cmdline.append('/usr/bin/sudo')
|
||||
if env is not None:
|
||||
cmdline.append('--preserve-env=' + ','.join(mod_env.keys()))
|
||||
cmdline.extend(opts)
|
||||
cmdline.extend(cmd)
|
||||
return await self._run(cmdline, *args, **kwargs)
|
||||
|
||||
async def _unlink(self, path: str) -> None:
|
||||
os.unlink(path)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class SSHClient(ExecContext):
|
|||
class Caps(Flag):
|
||||
LogOutput = auto()
|
||||
Interactive = auto()
|
||||
Env = auto()
|
||||
ModEnv = auto()
|
||||
Wd = auto()
|
||||
|
||||
def __init__(self, uri: str, caps: Caps=Caps(0), *args, **kwargs) -> None:
|
||||
|
|
@ -41,7 +41,7 @@ class SSHClient(ExecContext):
|
|||
wd: str|None,
|
||||
verbose: bool,
|
||||
cmd_input: bytes|None,
|
||||
env: dict[str, str]|None,
|
||||
mod_env: dict[str, str]|None,
|
||||
interactive: bool,
|
||||
log_prefix: str
|
||||
) -> Result:
|
||||
|
|
@ -53,7 +53,7 @@ class SSHClient(ExecContext):
|
|||
wd: str|None,
|
||||
verbose: bool,
|
||||
cmd_input: bytes|None,
|
||||
env: dict[str, str]|None,
|
||||
mod_env: dict[str, str]|None,
|
||||
interactive: bool,
|
||||
log_prefix: str
|
||||
) -> Result:
|
||||
|
|
@ -82,7 +82,7 @@ class SSHClient(ExecContext):
|
|||
if interactive and not self.__caps & self.Caps.Interactive:
|
||||
raise NotImplementedError('Interactive SSH is not yet implemented')
|
||||
|
||||
if env is not None and not self.__caps & self.Caps.Env:
|
||||
if mod_env is not None and not self.__caps & self.Caps.ModEnv:
|
||||
raise NotImplementedError('Passing an environment to SSH commands is not yet implemented')
|
||||
|
||||
ret = await self._run_ssh(
|
||||
|
|
@ -90,7 +90,7 @@ class SSHClient(ExecContext):
|
|||
wd=wd,
|
||||
verbose=verbose,
|
||||
cmd_input=cmd_input,
|
||||
env=env,
|
||||
mod_env=mod_env,
|
||||
interactive=interactive,
|
||||
log_prefix=log_prefix
|
||||
)
|
||||
|
|
@ -103,13 +103,6 @@ class SSHClient(ExecContext):
|
|||
|
||||
return ret
|
||||
|
||||
async def _sudo(self, cmd: list[str], mod_env: dict[str, str], opts: list[str], *args, **kwargs) -> Result:
|
||||
if self.username != 'root':
|
||||
cmd = ['sudo', *opts, *cmd]
|
||||
if mod_env:
|
||||
log(WARNING, f'Modifying environment over SSH is not implemented, ignored')
|
||||
return await self._run(cmd, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def hostname(self) -> str|None:
|
||||
return self.__hostname
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class AsyncSSH(Base):
|
|||
|
||||
super().__init__(
|
||||
uri,
|
||||
caps=self.Caps.LogOutput | self.Caps.Wd | self.Caps.Interactive | self.Caps.Env,
|
||||
caps = self.Caps.LogOutput | self.Caps.Wd | self.Caps.Interactive | self.Caps.ModEnv,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
@ -34,7 +34,8 @@ class AsyncSSH(Base):
|
|||
self.term_type = term_type or os.environ.get("TERM", "xterm")
|
||||
self.connect_timeout = connect_timeout
|
||||
|
||||
def _connect_kwargs(self) -> dict:
|
||||
def _connect_kwargs(self, hide_secrets: bool=False) -> dict:
|
||||
|
||||
kwargs: dict = {
|
||||
"host": self.hostname,
|
||||
"port": self.port,
|
||||
|
|
@ -47,7 +48,10 @@ class AsyncSSH(Base):
|
|||
if self.known_hosts is not _USE_DEFAULT_KNOWN_HOSTS:
|
||||
kwargs["known_hosts"] = self.known_hosts
|
||||
|
||||
return {k: v for k, v in kwargs.items() if v is not None}
|
||||
ret = {k: v for k, v in kwargs.items() if v is not None}
|
||||
if hide_secrets and 'password' in kwargs:
|
||||
kwargs['password'] = '<hidden>'
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def _build_remote_command(cmd: list[str], wd: str | None) -> str:
|
||||
|
|
@ -61,29 +65,6 @@ class AsyncSSH(Base):
|
|||
|
||||
return f"/bin/sh -lc {shlex.quote(inner)}"
|
||||
|
||||
@staticmethod
|
||||
def _merge_env_into_forwarded_args(
|
||||
args: tuple,
|
||||
kwargs: dict,
|
||||
mod_env: dict[str, str],
|
||||
) -> tuple[tuple, dict]:
|
||||
args = list(args)
|
||||
kwargs = dict(kwargs)
|
||||
|
||||
if "env" in kwargs:
|
||||
base_env = kwargs["env"]
|
||||
merged_env = dict(base_env or {})
|
||||
merged_env.update(mod_env)
|
||||
kwargs["env"] = merged_env or None
|
||||
elif len(args) >= 4:
|
||||
base_env = args[3]
|
||||
merged_env = dict(base_env or {})
|
||||
merged_env.update(mod_env)
|
||||
args[3] = merged_env or None
|
||||
else:
|
||||
kwargs["env"] = dict(mod_env) if mod_env else None
|
||||
return tuple(args), kwargs
|
||||
|
||||
@staticmethod
|
||||
def _has_local_tty() -> bool:
|
||||
try:
|
||||
|
|
@ -143,14 +124,15 @@ class AsyncSSH(Base):
|
|||
cmd: list[str],
|
||||
wd: str | None,
|
||||
cmd_input: bytes | None,
|
||||
env: dict[str, str] | None,
|
||||
mod_env: dict[str, str] | None,
|
||||
) -> Result:
|
||||
|
||||
command = self._build_remote_command(cmd, wd)
|
||||
stdout_parts: list[bytes] = []
|
||||
|
||||
proc = await conn.create_process(
|
||||
command=command,
|
||||
env=env,
|
||||
env=mod_env,
|
||||
stdin=asyncssh.PIPE,
|
||||
stdout=asyncssh.PIPE,
|
||||
stderr=asyncssh.STDOUT,
|
||||
|
|
@ -301,7 +283,7 @@ class AsyncSSH(Base):
|
|||
wd: str | None,
|
||||
verbose: bool,
|
||||
cmd_input: bytes | None,
|
||||
env: dict[str, str] | None,
|
||||
mod_env: dict[str, str] | None,
|
||||
log_prefix: str,
|
||||
) -> Result:
|
||||
command = self._build_remote_command(cmd, wd)
|
||||
|
|
@ -311,7 +293,7 @@ class AsyncSSH(Base):
|
|||
|
||||
proc = await conn.create_process(
|
||||
command=command,
|
||||
env=env,
|
||||
env=mod_env,
|
||||
stdin=asyncssh.PIPE if cmd_input is not None else asyncssh.DEVNULL,
|
||||
stdout=asyncssh.PIPE,
|
||||
stderr=asyncssh.STDOUT,
|
||||
|
|
@ -353,7 +335,7 @@ class AsyncSSH(Base):
|
|||
wd: str | None,
|
||||
verbose: bool,
|
||||
cmd_input: bytes | None,
|
||||
env: dict[str, str] | None,
|
||||
mod_env: dict[str, str] | None,
|
||||
interactive: bool,
|
||||
log_prefix: str,
|
||||
) -> Result:
|
||||
|
|
@ -364,7 +346,7 @@ class AsyncSSH(Base):
|
|||
cmd=cmd,
|
||||
wd=wd,
|
||||
cmd_input=cmd_input,
|
||||
env=env,
|
||||
mod_env=mod_env,
|
||||
)
|
||||
|
||||
return await self._run_captured_pty_on_conn(
|
||||
|
|
@ -373,7 +355,7 @@ class AsyncSSH(Base):
|
|||
wd=wd,
|
||||
verbose=verbose,
|
||||
cmd_input=cmd_input,
|
||||
env=env,
|
||||
mod_env=mod_env,
|
||||
log_prefix=log_prefix,
|
||||
)
|
||||
|
||||
|
|
@ -389,7 +371,7 @@ class AsyncSSH(Base):
|
|||
|
||||
proc = await conn.create_process(
|
||||
command=command,
|
||||
env=env,
|
||||
env=mod_env,
|
||||
stdin=stdin_mode,
|
||||
stdout=asyncssh.PIPE,
|
||||
stderr=asyncssh.PIPE,
|
||||
|
|
@ -443,48 +425,26 @@ class AsyncSSH(Base):
|
|||
wd: str | None,
|
||||
verbose: bool,
|
||||
cmd_input: str | None,
|
||||
env: dict[str, str] | None,
|
||||
mod_env: dict[str, str] | None,
|
||||
interactive: bool,
|
||||
log_prefix: str,
|
||||
) -> Result:
|
||||
async with asyncssh.connect(**self._connect_kwargs()) as conn:
|
||||
return await self._run_on_conn(
|
||||
conn=conn,
|
||||
cmd=cmd,
|
||||
wd=wd,
|
||||
verbose=verbose,
|
||||
cmd_input=cmd_input,
|
||||
env=env,
|
||||
interactive=interactive,
|
||||
log_prefix=log_prefix,
|
||||
)
|
||||
|
||||
async def _sudo(
|
||||
self,
|
||||
cmd: list[str],
|
||||
mod_env: dict[str, str],
|
||||
opts: list[str],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> Result:
|
||||
args, kwargs = self._merge_env_into_forwarded_args(args, kwargs, mod_env)
|
||||
|
||||
async with asyncssh.connect(**self._connect_kwargs()) as conn:
|
||||
uid_result = await conn.run("id -u", check=False)
|
||||
is_root = (
|
||||
uid_result.exit_status == 0
|
||||
and isinstance(uid_result.stdout, str)
|
||||
and uid_result.stdout.strip() == "0"
|
||||
)
|
||||
|
||||
cmdline: list[str] = []
|
||||
|
||||
if not is_root:
|
||||
cmdline.append("/usr/bin/sudo")
|
||||
if mod_env:
|
||||
cmdline.append("--preserve-env=" + ",".join(mod_env.keys()))
|
||||
cmdline.extend(opts)
|
||||
|
||||
cmdline.extend(cmd)
|
||||
|
||||
return await self._run_on_conn(conn, cmdline, *args, **kwargs)
|
||||
try:
|
||||
async with asyncssh.connect(**self._connect_kwargs()) as conn:
|
||||
return await self._run_on_conn(
|
||||
conn=conn,
|
||||
cmd=cmd,
|
||||
wd=wd,
|
||||
verbose=verbose,
|
||||
cmd_input=cmd_input,
|
||||
mod_env=mod_env,
|
||||
interactive=interactive,
|
||||
log_prefix=log_prefix,
|
||||
)
|
||||
except Exception as e:
|
||||
msg = f'-------------------- Failed to run command {" ".join(cmd)} ({e})'
|
||||
log(ERR, ',', msg)
|
||||
for key, val in self._connect_kwargs(hide_secrets=True).items():
|
||||
log(ERR, f'| {key:<20} = {val}')
|
||||
log(ERR, '`', msg)
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class Exec(Base):
|
|||
self.__askpass_orig: dict[str, str|None] = dict()
|
||||
super().__init__(
|
||||
uri = uri,
|
||||
caps = self.Caps.Env,
|
||||
caps = self.Caps.ModEnv,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ class Exec(Base):
|
|||
wd: str|None,
|
||||
verbose: bool,
|
||||
cmd_input: bytes|None,
|
||||
env: dict[str, str]|None,
|
||||
mod_env: dict[str, str]|None,
|
||||
interactive: bool,
|
||||
log_prefix: str
|
||||
) -> Result:
|
||||
|
|
@ -57,8 +57,8 @@ class Exec(Base):
|
|||
if cmd_input is None:
|
||||
cmd_input = InputMode.Interactive if interactive else InputMode.NonInteractive
|
||||
opts: dict[str, str] = []
|
||||
if env:
|
||||
for key, val in env.items():
|
||||
if mod_env:
|
||||
for key, val in mod_env.items():
|
||||
opts.extend(['-o', f'SetEnv {key}="{val}"'])
|
||||
if self.username:
|
||||
opts.extend(['-l', self.username])
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class Paramiko(Base):
|
|||
super().__init__(
|
||||
uri,
|
||||
*args,
|
||||
caps = self.Caps.Env,
|
||||
caps = self.Caps.ModEnv,
|
||||
**kwargs
|
||||
)
|
||||
self.__timeout: float|None = None # Untested
|
||||
|
|
@ -55,14 +55,14 @@ class Paramiko(Base):
|
|||
wd: str | None,
|
||||
verbose: bool,
|
||||
cmd_input: str | None,
|
||||
env: dict[str, str] | None,
|
||||
mod_env: dict[str, str] | None,
|
||||
interactive: bool,
|
||||
log_prefix: str,
|
||||
) -> Result:
|
||||
try:
|
||||
kwargs: [str, Any] = {}
|
||||
if env is not None:
|
||||
kwargs['environment'] = env
|
||||
if mod_env is not None:
|
||||
kwargs['environment'] = mod_env
|
||||
stdin, stdout, stderr = self.__ssh.exec_command(
|
||||
join_cmd(cmd),
|
||||
timeout=self.__timeout,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue