cmds.pkg.CmdVerify: Add command
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m24s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m43s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 4m48s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m38s
CI / Packaging test (push) Successful in 0s

Add the "packages verify" command: it runs Distro.verify() with the app's
package filter and prints one line per flagged file.

Signed-off-by: Jan Lindemann <jan@janware.com>
Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v1.2.0
This commit is contained in:
Jan Lindemann 2026-10-06 18:12:38 +02:00
commit 8106d4a7b0
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61
5 changed files with 173 additions and 1 deletions

View file

@ -0,0 +1,29 @@
from __future__ import annotations
from typing import TYPE_CHECKING, override
from .Cmd import Cmd, Parent
if TYPE_CHECKING:
from argparse import ArgumentParser, Namespace
class CmdVerify(Cmd): # export
def __init__(self, parent: Parent) -> None:
super().__init__(
parent,
'verify',
help = (
'Verify the integrity of installed packages: report files'
' modified or deleted outside of the package manager'
)
)
@override
def add_arguments(self, parser: ArgumentParser) -> None:
super().add_arguments(parser)
@override
async def _run(self, args: Namespace) -> None:
for file in await self.distro.verify():
print(file)

View file

@ -66,6 +66,8 @@ Available subcommands of packages:
reboot-required Check whether the machine needs rebooting
refresh Refresh the distribution's notion of available packages
select Select packages by filter
verify Verify the integrity of installed packages: report files
modified or deleted outside of the package manager
============= Running: jw-pkg.py -t ../../../.. --log-level info packages delete --help
usage: jw-pkg.py packages delete [-h] [names ...]
@ -140,6 +142,14 @@ Select packages by filter
positional arguments:
filter Package filter string
options:
-h, --help show this help message and exit
============= Running: jw-pkg.py -t ../../../.. --log-level info packages verify --help
usage: jw-pkg.py packages verify [-h]
Verify the integrity of installed packages: report files modified or deleted
outside of the package manager
options:
-h, --help show this help message and exit
============= Running: jw-pkg.py -t ../../../.. --log-level info platform --help

View file

@ -0,0 +1,7 @@
TOPDIR = ../../../../../../../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-run.mk
all:
test: run

View file

@ -0,0 +1,81 @@
from __future__ import annotations
import asyncio
from typing import override
from jw.pkg.lib.ec.Local import Local
from jw.pkg.lib.pm.dpkg import list_files
from jw.pkg.lib.Result import Result
class CannedLocal(Local):
def __init__(self, stdout: str) -> None:
super().__init__('local')
self.stdout = stdout
self.cmds: list[list[str]] = []
@override
async def _run(
self,
cmd: list[str],
wd: str | None,
verbose: bool,
cmd_input: bytes | None,
mod_env: dict[str, str] | None,
interactive: bool,
log_prefix: str,
) -> Result:
self.cmds.append(cmd)
return Result(stdout = self.stdout.encode(), status = 0)
# -- One listing over several packages
cl = CannedLocal('/usr/bin/b\n/etc/b/b.conf.jw-tmpl\n')
assert asyncio.run(list_files(['a', 'b'], ec = cl)) == \
['/usr/bin/b', '/etc/b/b.conf.jw-tmpl']
assert cl.cmds[-1] == [
'/usr/bin/dpkg',
'-L',
'a',
'b',
]
# -- The unfiltered selection is 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
class CannedTwoStage(Local):
def __init__(self, query: str, listing: str) -> None:
super().__init__('local')
self.query = query
self.listing = listing
self.cmds: list[list[str]] = []
@override
async def _run(
self,
cmd: list[str],
wd: str | None,
verbose: bool,
cmd_input: bytes | None,
mod_env: dict[str, str] | None,
interactive: bool,
log_prefix: str,
) -> Result:
self.cmds.append(cmd)
out = self.query if '-W' in cmd else self.listing
return Result(stdout = out.encode(), status = 0)
cl2 = CannedTwoStage('a\nb\n', 'a:\n/usr/bin/a\n\nb:\n/etc/b/b.conf.jw-tmpl\n')
assert asyncio.run(list_files(None, ec = cl2)) == \
['/usr/bin/a', '/etc/b/b.conf.jw-tmpl']
assert cl2.cmds == [
['/usr/bin/dpkg-query', '-W', '-f=${?installed:binary:Package}\n'],
['/usr/bin/dpkg-query', '-L', 'a', 'b'],
]
# -- An empty selection lists nothing and runs no query
cl3 = CannedTwoStage('', '')
assert asyncio.run(list_files([], ec = cl3)) == []
assert cl3.cmds == []

View file

@ -1,7 +1,13 @@
from __future__ import annotations
import asyncio
from typing import override
from jw.pkg.lib.ec.Local import Local
from jw.pkg.lib.FlaggedFile import FlaggedFile
from jw.pkg.lib.pm.rpm import _parse_verify_output
from jw.pkg.lib.pm.rpm import _parse_verify_output, list_files
from jw.pkg.lib.Result import Result
# -- _parse_verify_output --
@ -56,3 +62,42 @@ f = FlaggedFile('pkg', '/etc/foo', 'SM5....T.')
assert repr(f) == 'pkg /etc/foo SM5....T.'
print('All rpm pm tests passed')
# -- list_files --
class CannedLocal(Local):
def __init__(self, stdout: str) -> None:
super().__init__('local')
self.stdout = stdout
self.cmds: list[list[str]] = []
@override
async def _run(
self,
cmd: list[str],
wd: str | None,
verbose: bool,
cmd_input: bytes | None,
mod_env: dict[str, str] | None,
interactive: bool,
log_prefix: str,
) -> Result:
self.cmds.append(cmd)
return Result(stdout = self.stdout.encode(), status = 0)
# -- One query over several packages, the output split into lines
cl = CannedLocal('/usr/bin/a\n/etc/a/a.conf.jw-tmpl\n')
assert asyncio.run(list_files(['a', 'b'], ec = cl)) == \
['/usr/bin/a', '/etc/a/a.conf.jw-tmpl']
assert cl.cmds[-1] == ['/usr/bin/rpm', '-ql', 'a', 'b']
# -- The unfiltered selection is one query without package names
cl = CannedLocal('/usr/bin/a\n/etc/a/a.conf.jw-tmpl\n')
assert asyncio.run(list_files(None, ec = cl)) == ['/usr/bin/a', '/etc/a/a.conf.jw-tmpl']
assert cl.cmds[-1] == ['/usr/bin/rpm', '-qal']
# -- An empty selection lists nothing and runs no query
cl = CannedLocal('')
assert asyncio.run(list_files([], ec = cl)) == []
assert cl.cmds == []