cmds.pkg.CmdVerify: Add command #142
9 changed files with 73 additions and 30 deletions
lib.Distro.pkg_files(): Take list[str] | None
Distro.pkg_files() takes a package name and returns a list of files in that package. This commit generalizes that - pass None to it and get the list of all packaged files in the system, and pass a list of strings to retrieve all files belonging to the named packages. The same arguments are propagated to _pkg_files(), which in turn propagates them to list_files(). This allows faster generation of file lists than before. Having to retrieve a list of matching packages and query each package for its contained files can be collapsed into one or two backend calls. Signed-off-by: Jan Lindemann <jan@janware.com>
commit
2fad5d189c
|
|
@ -471,11 +471,18 @@ class Distro(abc.ABC):
|
||||||
# -- pkg_files
|
# -- pkg_files
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def _pkg_files(self, name: str) -> Iterable[str]:
|
async def _pkg_files(self, names: list[str] | None) -> Iterable[str]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def pkg_files(self, name: str) -> Iterable[str]:
|
async def pkg_files(self, names: str | list[str] | None = None) -> Iterable[str]:
|
||||||
if not name:
|
# -- The file paths of the packages selected by the
|
||||||
log(WARNING, 'No package specified for inspection')
|
# argument: None, the default selection, filtered to the
|
||||||
return []
|
# app's packages when a filter is set, all installed
|
||||||
return await self._pkg_files(name)
|
# packages otherwise; [], no packages; [names], the
|
||||||
|
# listed packages; a str names one package, accepted for
|
||||||
|
# compatibility with the single-package call
|
||||||
|
if isinstance(names, str):
|
||||||
|
names = [names]
|
||||||
|
if names is None and self.default_pkg_filter is not None:
|
||||||
|
names = [p.name for p in await self.select()]
|
||||||
|
return await self._pkg_files(names)
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class Distro(Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _pkg_files(self, name: str) -> Iterable[str]:
|
async def _pkg_files(self, names: list[str] | None) -> Iterable[str]:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'distro pkg ls yet implemented for Arch-like distributions'
|
'distro pkg ls yet implemented for Arch-like distributions'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -85,5 +85,5 @@ class Distro(Base):
|
||||||
await self.dpkg(['-P', *names], sudo = True)
|
await self.dpkg(['-P', *names], sudo = True)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _pkg_files(self, name: str) -> Iterable[str]:
|
async def _pkg_files(self, names: list[str] | None) -> Iterable[str]:
|
||||||
return await list_files(name, ec = self.ctx)
|
return await list_files(names, ec = self.ctx)
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ class Distro(Base):
|
||||||
await self.rpm(['-e', *names], sudo = True)
|
await self.rpm(['-e', *names], sudo = True)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _pkg_files(self, name: str) -> Iterable[str]:
|
async def _pkg_files(self, names: list[str] | None) -> Iterable[str]:
|
||||||
return await list_files(name, ec = self.ctx)
|
return await list_files(names, ec = self.ctx)
|
||||||
|
|
||||||
async def _download(self, name: str, tmp_dir: str) -> str:
|
async def _download(self, name: str, tmp_dir: str) -> str:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
|
|
|
||||||
|
|
@ -95,5 +95,5 @@ class Distro(Base):
|
||||||
await self.rpm(['-e', *names], sudo = True)
|
await self.rpm(['-e', *names], sudo = True)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _pkg_files(self, name: str) -> Iterable[str]:
|
async def _pkg_files(self, names: list[str] | None) -> Iterable[str]:
|
||||||
return await list_files(name, ec = self.ctx)
|
return await list_files(names, ec = self.ctx)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Iterable, cast
|
from typing import TYPE_CHECKING, Any, Iterable, cast
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..ExecContext import ExecContext
|
from ..ExecContext import ExecContext
|
||||||
|
|
@ -26,21 +26,25 @@ def meta_map() -> dict[str, str]:
|
||||||
return _meta_map
|
return _meta_map
|
||||||
|
|
||||||
async def _run(
|
async def _run(
|
||||||
cmd: list[str], sudo: bool = False, ec: ExecContext | None = None
|
cmd: list[str],
|
||||||
|
sudo: bool = False,
|
||||||
|
ec: ExecContext | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
) -> str:
|
) -> str:
|
||||||
return (
|
return (
|
||||||
await run_sudo(cmd)
|
await run_sudo(cmd) if sudo else await
|
||||||
if sudo else await run_cmd(cmd, ec = ec, cmd_input = InputMode.NonInteractive)
|
run_cmd(cmd, ec = ec, cmd_input = InputMode.NonInteractive, **kwargs)
|
||||||
).stdout_str
|
).stdout_str
|
||||||
|
|
||||||
async def run_dpkg( # export
|
async def run_dpkg( # export
|
||||||
args: list[str],
|
args: list[str],
|
||||||
sudo: bool = False,
|
sudo: bool = False,
|
||||||
ec: ExecContext | None = None
|
ec: ExecContext | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
) -> str:
|
) -> str:
|
||||||
cmd = ['/usr/bin/dpkg']
|
cmd = ['/usr/bin/dpkg']
|
||||||
cmd.extend(args)
|
cmd.extend(args)
|
||||||
return await _run(cmd, sudo, ec)
|
return await _run(cmd, sudo, ec, **kwargs)
|
||||||
|
|
||||||
async def run_dpkg_query( # export
|
async def run_dpkg_query( # export
|
||||||
args: list[str],
|
args: list[str],
|
||||||
|
|
@ -61,6 +65,26 @@ async def query_packages(names: Iterable[str] = [],
|
||||||
specs = await run_dpkg_query(['-W', '-f=' + fmt_str, *names], sudo = False, ec = ec)
|
specs = await run_dpkg_query(['-W', '-f=' + fmt_str, *names], sudo = False, ec = ec)
|
||||||
return cast('Iterable[Package]', Package.parse_specs_str(specs))
|
return cast('Iterable[Package]', Package.parse_specs_str(specs))
|
||||||
|
|
||||||
async def list_files(pkg: str, ec: ExecContext | None = None) -> list[str]:
|
async def list_files(
|
||||||
file_list_str = await run_dpkg(['-L', pkg], sudo = False, ec = ec)
|
names: list[str] | None = None,
|
||||||
|
ec: ExecContext | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
# -- None: all installed packages. The standard interface, two
|
||||||
|
# queries: select the installed packages, then list their
|
||||||
|
# files in one command; the list output carries a
|
||||||
|
# package-name header and a blank line per package, both
|
||||||
|
# dropped. []: no packages, no command
|
||||||
|
if names is None:
|
||||||
|
names = (
|
||||||
|
await run_dpkg_query(
|
||||||
|
['-W', '-f=${?installed:binary:Package}\n'], sudo = False, ec = ec
|
||||||
|
)
|
||||||
|
).split()
|
||||||
|
if not names:
|
||||||
|
return []
|
||||||
|
stdout = await run_dpkg_query(['-L', *names], sudo = False, ec = ec)
|
||||||
|
return [line for line in stdout.splitlines() if line and not line.endswith(':')]
|
||||||
|
if not names:
|
||||||
|
return []
|
||||||
|
file_list_str = await run_dpkg(['-L', *names], sudo = False, ec = ec)
|
||||||
return file_list_str.splitlines()
|
return file_list_str.splitlines()
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,19 @@ async def query_packages( # export
|
||||||
)
|
)
|
||||||
return cast('Iterable[Package]', Package.parse_specs_str(specs))
|
return cast('Iterable[Package]', Package.parse_specs_str(specs))
|
||||||
|
|
||||||
async def list_files(pkg: str, ec: ExecContext | None = None) -> list[str]:
|
async def list_files(
|
||||||
|
names: list[str] | None = None,
|
||||||
|
ec: ExecContext | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
# -- None: all installed packages, one query without package
|
||||||
|
# names. []: no packages, no command
|
||||||
|
if names is None:
|
||||||
|
args = ['-qal']
|
||||||
|
elif not names:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
args = ['-ql', *names]
|
||||||
stdout = await run_rpm(
|
stdout = await run_rpm(
|
||||||
['-ql', pkg],
|
args, throw = True, sudo = False, mode = InputMode.NonInteractive, ec = ec
|
||||||
throw = True,
|
|
||||||
sudo = False,
|
|
||||||
mode = InputMode.NonInteractive,
|
|
||||||
ec = ec
|
|
||||||
)
|
)
|
||||||
return stdout.splitlines()
|
return stdout.splitlines()
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,13 @@ class FakeDistro(SuseDistro):
|
||||||
return [Package(name) for name in names if name in self.__files]
|
return [Package(name) for name in names if name in self.__files]
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _pkg_files(self, name: str) -> list[str]:
|
async def _pkg_files(self, names: list[str] | None) -> list[str]:
|
||||||
return list(self.__files.get(name, []))
|
if names is None:
|
||||||
|
names = list(self.__files)
|
||||||
|
ret: list[str] = []
|
||||||
|
for n in names:
|
||||||
|
ret.extend(self.__files.get(n, []))
|
||||||
|
return ret
|
||||||
|
|
||||||
def run_cli(distro: FakeDistro, argv: list[str]) -> tuple[list[str], list[str]]:
|
def run_cli(distro: FakeDistro, argv: list[str]) -> tuple[list[str], list[str]]:
|
||||||
"""Run the real jw-pkg CLI in-process, return stdout and stderr
|
"""Run the real jw-pkg CLI in-process, return stdout and stderr
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class DummyDistro(Distro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _pkg_files(self, name: str) -> Iterable[str]:
|
async def _pkg_files(self, names: list[str] | None) -> Iterable[str]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def distro(os_release_str: str) -> Distro:
|
def distro(os_release_str: str) -> Distro:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue