From 3e897f4df8c61db87bc6daec296179949591aa0e Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Thu, 5 Mar 2026 17:33:52 +0100 Subject: [PATCH] lib.Distro, ExecContext: Add classes, refactor lib.distro The code below lib.distro, as left behind by the previous commit, is geared towards being directly used as a command-line API. This commit introduces the abstract base class Distro, a proxy for distribution-specific interactions. The proxy abstracts distro specifics into an API with proper method prototypes, not argparse.Namespace contents, and can thus be more easily driven by arbitrary code. The Distro class is initialized with a member variable of type ExecContext, another new class introduced by this commit. It is designed to abstract the communication channel to the distribution instance. Currently only one specialization exists, Local, which interacts with the distribution and root file system it is running in, but is planned to be subclassed to support interaction via SSH, serial, chroot, or chains thereof. Signed-off-by: Jan Lindemann --- make/projects-dir.mk | 2 +- make/topdir.mk | 2 +- src/python/jw/pkg/App.py | 7 + src/python/jw/pkg/cmds/DistroBase.py | 25 ++-- src/python/jw/pkg/cmds/distro/Cmd.py | 58 ++------- src/python/jw/pkg/cmds/distro/CmdDelete.py | 2 +- src/python/jw/pkg/cmds/distro/CmdDup.py | 3 +- src/python/jw/pkg/cmds/distro/CmdInstall.py | 4 +- .../jw/pkg/cmds/distro/CmdRebootRequired.py | 2 +- src/python/jw/pkg/cmds/distro/CmdRefresh.py | 2 +- src/python/jw/pkg/cmds/distro/CmdSelect.py | 3 +- src/python/jw/pkg/cmds/distro/pkg/Cmd.py | 6 - src/python/jw/pkg/cmds/distro/pkg/CmdLs.py | 2 +- src/python/jw/pkg/cmds/distro/pkg/CmdMeta.py | 12 +- src/python/jw/pkg/lib/Distro.py | 122 ++++++++++++++++++ src/python/jw/pkg/lib/ExecContext.py | 33 +++++ src/python/jw/pkg/lib/distros/Backend.py | 29 ----- src/python/jw/pkg/lib/distros/BeDelete.py | 16 --- src/python/jw/pkg/lib/distros/BeDup.py | 16 --- src/python/jw/pkg/lib/distros/BeInstall.py | 16 --- src/python/jw/pkg/lib/distros/BePkg.py | 31 ----- .../jw/pkg/lib/distros/BeRebootRequired.py | 16 --- src/python/jw/pkg/lib/distros/BeRefresh.py | 16 --- src/python/jw/pkg/lib/distros/BeSelect.py | 26 ---- src/python/jw/pkg/lib/distros/Util.py | 25 ---- src/python/jw/pkg/lib/distros/arch/Distro.py | 51 ++++++++ src/python/jw/pkg/lib/distros/arch/Dup.py | 17 --- src/python/jw/pkg/lib/distros/arch/Install.py | 18 --- src/python/jw/pkg/lib/distros/arch/Refresh.py | 14 -- src/python/jw/pkg/lib/distros/arch/Util.py | 16 --- .../jw/pkg/lib/distros/debian/Delete.py | 14 -- .../jw/pkg/lib/distros/debian/Distro.py | 75 +++++++++++ src/python/jw/pkg/lib/distros/debian/Dup.py | 18 --- .../jw/pkg/lib/distros/debian/Install.py | 19 --- src/python/jw/pkg/lib/distros/debian/Pkg.py | 20 --- .../pkg/lib/distros/debian/RebootRequired.py | 29 ----- .../jw/pkg/lib/distros/debian/Refresh.py | 14 -- .../jw/pkg/lib/distros/debian/Select.py | 17 --- src/python/jw/pkg/lib/distros/debian/Util.py | 22 ---- src/python/jw/pkg/lib/distros/redhat/Dup.py | 17 --- .../jw/pkg/lib/distros/redhat/Install.py | 18 --- .../jw/pkg/lib/distros/redhat/Refresh.py | 15 --- src/python/jw/pkg/lib/distros/redhat/Util.py | 14 -- src/python/jw/pkg/lib/distros/suse/Delete.py | 14 -- src/python/jw/pkg/lib/distros/suse/Distro.py | 63 +++++++++ src/python/jw/pkg/lib/distros/suse/Dup.py | 17 --- src/python/jw/pkg/lib/distros/suse/Install.py | 15 --- src/python/jw/pkg/lib/distros/suse/Pkg.py | 20 --- .../jw/pkg/lib/distros/suse/RebootRequired.py | 22 ---- src/python/jw/pkg/lib/distros/suse/Refresh.py | 14 -- src/python/jw/pkg/lib/distros/suse/Select.py | 17 --- src/python/jw/pkg/lib/distros/suse/Util.py | 25 ---- src/python/jw/pkg/lib/ec/Local.py | 29 +++++ src/python/jw/pkg/lib/ec/Makefile | 4 + src/python/jw/pkg/lib/util.py | 22 +--- 55 files changed, 426 insertions(+), 720 deletions(-) create mode 100644 src/python/jw/pkg/lib/Distro.py create mode 100644 src/python/jw/pkg/lib/ExecContext.py delete mode 100644 src/python/jw/pkg/lib/distros/Backend.py delete mode 100644 src/python/jw/pkg/lib/distros/BeDelete.py delete mode 100644 src/python/jw/pkg/lib/distros/BeDup.py delete mode 100644 src/python/jw/pkg/lib/distros/BeInstall.py delete mode 100644 src/python/jw/pkg/lib/distros/BePkg.py delete mode 100644 src/python/jw/pkg/lib/distros/BeRebootRequired.py delete mode 100644 src/python/jw/pkg/lib/distros/BeRefresh.py delete mode 100644 src/python/jw/pkg/lib/distros/BeSelect.py delete mode 100644 src/python/jw/pkg/lib/distros/Util.py create mode 100644 src/python/jw/pkg/lib/distros/arch/Distro.py delete mode 100644 src/python/jw/pkg/lib/distros/arch/Dup.py delete mode 100644 src/python/jw/pkg/lib/distros/arch/Install.py delete mode 100644 src/python/jw/pkg/lib/distros/arch/Refresh.py delete mode 100644 src/python/jw/pkg/lib/distros/arch/Util.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Delete.py create mode 100644 src/python/jw/pkg/lib/distros/debian/Distro.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Dup.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Install.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Pkg.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/RebootRequired.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Refresh.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Select.py delete mode 100644 src/python/jw/pkg/lib/distros/debian/Util.py delete mode 100644 src/python/jw/pkg/lib/distros/redhat/Dup.py delete mode 100644 src/python/jw/pkg/lib/distros/redhat/Install.py delete mode 100644 src/python/jw/pkg/lib/distros/redhat/Refresh.py delete mode 100644 src/python/jw/pkg/lib/distros/redhat/Util.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Delete.py create mode 100644 src/python/jw/pkg/lib/distros/suse/Distro.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Dup.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Install.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Pkg.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/RebootRequired.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Refresh.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Select.py delete mode 100644 src/python/jw/pkg/lib/distros/suse/Util.py create mode 100644 src/python/jw/pkg/lib/ec/Local.py create mode 100644 src/python/jw/pkg/lib/ec/Makefile diff --git a/make/projects-dir.mk b/make/projects-dir.mk index 1eecdefb..65566146 100644 --- a/make/projects-dir.mk +++ b/make/projects-dir.mk @@ -116,7 +116,7 @@ ifeq ($(TIME),) endif JW_PKG_PY_PROJECTS = $(TIME) $(JW_PKG_PY) projects JW_PKG_PY_BUILD = $(JW_PKG_PY_PROJECTS) build $(JW_PKG_PY_EXTRA_BUILD_OPTS) -PKG_MANAGER ?= $(TIME) $(JW_PKG_PY) distro --interactive=$(INTERACTIVE) +PKG_MANAGER ?= $(TIME) $(JW_PKG_PY) --interactive=$(INTERACTIVE) distro ifneq ($(origin PROJECTS_DIR_REMOTE_BASE),undefined) PGIT_SH += --remote-base $(PROJECTS_DIR_REMOTE_BASE) diff --git a/make/topdir.mk b/make/topdir.mk index d86b1a0e..c6cf76a0 100644 --- a/make/topdir.mk +++ b/make/topdir.mk @@ -22,7 +22,7 @@ GIT_MAIN_BRANCH ?= master OPT_JANWARE_PROJECT ?= -j INTERACTIVE ?= auto -PKG_MANAGER ?= $(JW_PKG_PY) distro --interactive=$(INTERACTIVE) +PKG_MANAGER ?= $(JW_PKG_PY) --interactive=$(INTERACTIVE) distro ifeq ($(OPT_JANWARE_PROJECT),-j) REMOTE_GIT_FLAVOUR ?= proj diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index b602af92..08066a0d 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -265,6 +265,13 @@ class App(Base): self.__opt_interactive = sys.stdin.isatty() return self.__opt_interactive + @property + def exec_context(self) -> str: + if self.__exec_context is None: + from .lib.ec.Local import Local + self.__exec_context = Local(interactive=self.interactive) + return self.__exec_context + @property def top_name(self): return self.__top_name diff --git a/src/python/jw/pkg/cmds/DistroBase.py b/src/python/jw/pkg/cmds/DistroBase.py index 29c5a90b..26f7d3fd 100644 --- a/src/python/jw/pkg/cmds/DistroBase.py +++ b/src/python/jw/pkg/cmds/DistroBase.py @@ -3,6 +3,7 @@ import re, sys from argparse import ArgumentParser +from ..lib.Distro import Distro from ..App import App from .Cmd import Cmd as Base @@ -12,7 +13,16 @@ class DistroBase(Base): # export self.__id = None super().__init__(parent, name, help) self._add_subcommands() - self.__interactive: bool|None = None + self.__distro: Distro|None = None + + @property + def distro(self) -> Distro: + if self.__distro is None: + ret = Distro.instantiate(self.distro_id, ec=self.app.exec_context) + self.__distro = ret + return self.__distro + + # --------------- legacy methods @property def distro_id(self): @@ -28,19 +38,6 @@ class DistroBase(Base): # export def add_arguments(self, p: ArgumentParser) -> None: super().add_arguments(p) p.add_argument('--id', default=None, help='Distribution ID (default is taken from /etc/os-release)') - p.add_argument('--interactive', choices=['true', 'false', 'auto'], default='true', help="Wait for user input or try to proceed unattended") - - @property - def interactive(self) -> bool: - if self.__interactive is None: - match self.app.args.interactive: - case 'true': - self.__interactive = True - case 'false': - self.__interactive = False - case 'auto': - self.__interactive = sys.stdin.isatty() - return self.__interactive async def _run(self, args): # Missing subcommand diff --git a/src/python/jw/pkg/cmds/distro/Cmd.py b/src/python/jw/pkg/cmds/distro/Cmd.py index 70739f23..b6da5d51 100644 --- a/src/python/jw/pkg/cmds/distro/Cmd.py +++ b/src/python/jw/pkg/cmds/distro/Cmd.py @@ -1,64 +1,28 @@ # -*- coding: utf-8 -*- +from __future__ import annotations + +from typing import TYPE_CHECKING + import os, importlib from ...lib.log import * -from ...lib.distros.Util import Util + +if TYPE_CHECKING: + from ...lib.Distro import Distro from ..Cmd import Cmd as Base from ..CmdDistro import CmdDistro class Cmd(Base): # export - from ...lib.distros.Backend import Backend - def __init__(self, parent: CmdDistro, name: str, help: str) -> None: super().__init__(parent, name, help) - self.__backend_path: str|None = None - self.__util: Util|None = None - self.__backend: Backend|None = None + + @property + def distro(self) -> Distro: + return self.parent.distro @property def distro_id(self) -> str: return self.parent.distro_id - - @property - def interactive(self) -> bool: - return self.parent.interactive - - @property - def _backend_path(self): - if self.__backend_path is None: - backend_id = self.parent.distro_id.lower().replace('-', '_') - match backend_id: - case 'ubuntu' | 'raspbian' | 'kali': - backend_id = 'debian' - case 'centos': - backend_id = 'redhat' - case 'opensuse' | 'suse': - backend_id = 'suse' - self.__backend_path = 'jw.pkg.lib.distros.' + backend_id + '.' - return self.__backend_path - - def _instantiate(self, name: str, *args, **kwargs): - module_path = self._backend_path + name - try: - module = importlib.import_module(module_path) - except Exception as e: - log(ERR, f'Failed to import module {module_path} ({str(e)})') - raise - cls = getattr(module, name) - return cls(self, *args, **kwargs) - - @property - def util(self) -> Util: - if self.__util is None: - self.__util = self._instantiate('Util') - return self.__util - - @property - def _backend(self) -> Backend: - if self.__backend is None: - name = self.__class__.__name__[3:] # Get rid of "Cmd" - self.__backend = self._instantiate(name) - return self.__backend diff --git a/src/python/jw/pkg/cmds/distro/CmdDelete.py b/src/python/jw/pkg/cmds/distro/CmdDelete.py index 193a2e13..9f8d7658 100644 --- a/src/python/jw/pkg/cmds/distro/CmdDelete.py +++ b/src/python/jw/pkg/cmds/distro/CmdDelete.py @@ -15,4 +15,4 @@ class CmdDelete(Cmd): # export parser.add_argument("names", nargs="*", help="Names of packages to be deleted") async def _run(self, args: Namespace) -> None: - return await self._backend.run(args) + return await self.distro.delete(args.names) diff --git a/src/python/jw/pkg/cmds/distro/CmdDup.py b/src/python/jw/pkg/cmds/distro/CmdDup.py index cd3935d0..af236ce4 100644 --- a/src/python/jw/pkg/cmds/distro/CmdDup.py +++ b/src/python/jw/pkg/cmds/distro/CmdDup.py @@ -14,6 +14,5 @@ class CmdDup(Cmd): # export super().add_arguments(parser) parser.add_argument('--download-only', default=False, action='store_true', help='Only download packages from the repos, don\'t install them, yet') - async def _run(self, args: Namespace) -> None: - return await self._backend.run(args) + return await self.distro.dup(download_only=args.download_only) diff --git a/src/python/jw/pkg/cmds/distro/CmdInstall.py b/src/python/jw/pkg/cmds/distro/CmdInstall.py index 286e77f0..7f3a9074 100644 --- a/src/python/jw/pkg/cmds/distro/CmdInstall.py +++ b/src/python/jw/pkg/cmds/distro/CmdInstall.py @@ -12,8 +12,8 @@ class CmdInstall(Cmd): # export def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) - parser.add_argument("packages", nargs="*", help="Packages to be installed") + parser.add_argument("names", nargs="*", help="Packages to be installed") parser.add_argument('--only-update', default=False, action='store_true', help='Only update the listed packages, don\'t install them') async def _run(self, args: Namespace) -> None: - return await self._backend.run(args) + return await self.distro.install(args.names, only_update=args.only_update) diff --git a/src/python/jw/pkg/cmds/distro/CmdRebootRequired.py b/src/python/jw/pkg/cmds/distro/CmdRebootRequired.py index 21abc89e..e19bbccb 100644 --- a/src/python/jw/pkg/cmds/distro/CmdRebootRequired.py +++ b/src/python/jw/pkg/cmds/distro/CmdRebootRequired.py @@ -15,4 +15,4 @@ class CmdRebootRequired(Cmd): # export parser.add_argument('--verbose', default=False, action='store_true', help='Be chatty about the check') async def _run(self, args: Namespace) -> None: - return await self._backend.run(args) + return await self.distro.reboot_required(verbose=args.verbose) diff --git a/src/python/jw/pkg/cmds/distro/CmdRefresh.py b/src/python/jw/pkg/cmds/distro/CmdRefresh.py index 5dc125a6..51669df4 100644 --- a/src/python/jw/pkg/cmds/distro/CmdRefresh.py +++ b/src/python/jw/pkg/cmds/distro/CmdRefresh.py @@ -14,4 +14,4 @@ class CmdRefresh(Cmd): # export super().add_arguments(parser) async def _run(self, args: Namespace) -> None: - return await self._backend.run(args) + return await self.distro.ref() diff --git a/src/python/jw/pkg/cmds/distro/CmdSelect.py b/src/python/jw/pkg/cmds/distro/CmdSelect.py index 544ff788..db5c74dc 100644 --- a/src/python/jw/pkg/cmds/distro/CmdSelect.py +++ b/src/python/jw/pkg/cmds/distro/CmdSelect.py @@ -4,7 +4,6 @@ from argparse import Namespace, ArgumentParser import re from ...lib.Package import Package -from ...lib.distros.BeSelect import BeSelect from ..CmdDistro import CmdDistro from .Cmd import Cmd @@ -27,5 +26,5 @@ class CmdSelect(Cmd): # export async def _run(self, args: Namespace) -> None: # TODO: Semantics probably change heavily in the future - for p in self.filter_packages(args.filter, await self._backend.all_installed_packages): + for p in self.filter_packages(args.filter, await self.distro.select()): print(p.name) diff --git a/src/python/jw/pkg/cmds/distro/pkg/Cmd.py b/src/python/jw/pkg/cmds/distro/pkg/Cmd.py index bdd07682..9a2038ea 100644 --- a/src/python/jw/pkg/cmds/distro/pkg/Cmd.py +++ b/src/python/jw/pkg/cmds/distro/pkg/Cmd.py @@ -7,15 +7,9 @@ from ..CmdPkg import CmdPkg as Parent class Cmd(Base): # export - from ....lib.distros.Backend import Backend - def __init__(self, parent: Parent, name: str, help: str) -> None: super().__init__(parent, name, help) def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('names', nargs='*', help='Package names') - - @property - def _backend(self) -> Backend: - return self.parent._backend diff --git a/src/python/jw/pkg/cmds/distro/pkg/CmdLs.py b/src/python/jw/pkg/cmds/distro/pkg/CmdLs.py index cdfccb07..bccb41c1 100644 --- a/src/python/jw/pkg/cmds/distro/pkg/CmdLs.py +++ b/src/python/jw/pkg/cmds/distro/pkg/CmdLs.py @@ -15,4 +15,4 @@ class CmdLs(Cmd): # export async def _run(self, args: Namespace) -> None: for name in args.names: - print('\n'.join(await self._backend.files(name))) + print('\n'.join(await self.parent.distro.pkg_files(name))) diff --git a/src/python/jw/pkg/cmds/distro/pkg/CmdMeta.py b/src/python/jw/pkg/cmds/distro/pkg/CmdMeta.py index 0f1cf6ee..c2fdef22 100644 --- a/src/python/jw/pkg/cmds/distro/pkg/CmdMeta.py +++ b/src/python/jw/pkg/cmds/distro/pkg/CmdMeta.py @@ -14,10 +14,8 @@ class CmdMeta(Cmd): # export super().add_arguments(parser) async def _run(self, args: Namespace) -> None: - for name in args.names: - packages = await self._backend.meta_data([name]) - if packages: - assert len(packages) == 1 - if len(args.names) > 1: - print(f'-- {name}') - print(packages[0]) + packages = await self.distro.select(args.names) + for package in packages: + if len(args.names) > 1: + print(f'-- {name}') + print(package) diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py new file mode 100644 index 00000000..7cbf3eed --- /dev/null +++ b/src/python/jw/pkg/lib/Distro.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import Iterable + +import abc, importlib + +from .ExecContext import ExecContext, Result +from .Package import Package +from .log import * + +class Distro(abc.ABC): + + def __init__(self, ec: ExecContext): + assert ec is not None + self.__exec_context = ec + + # == Load + + @classmethod + def instantiate(cls, distro_id: str, *args, **kwargs): + backend_id = distro_id.lower().replace('-', '_') + match backend_id: + case 'ubuntu' | 'raspbian' | 'kali': + backend_id = 'debian' + case 'centos': + backend_id = 'redhat' + case 'opensuse' | 'suse': + backend_id = 'suse' + module_path = 'jw.pkg.lib.distros.' + backend_id + '.Distro' + try: + module = importlib.import_module(module_path) + except Exception as e: + log(ERR, f'Failed to import Distro module {module_path} ({str(e)})') + raise + cls = getattr(module, 'Distro') + return cls(*args, **kwargs) + + # == Convenience methods + + @property + def ctx(self) -> ExecContext: + return self.__exec_context + + def run(self, *args, **kwargs) -> Result: + return self.__exec_context.run(*args, **kwargs) + + def sudo(self, *args, **kwargs) -> Result: + return self.__exec_context.sudo(*args, **kwargs) + + @property + def interactive(self) -> bool: + return self.__exec_context.interactive + + # == Distribution specific methods + + # -- ref + + @abc.abstractmethod + async def _ref(self) -> None: + pass + + async def ref(self) -> None: + return await self._ref() + + # -- dup + + @abc.abstractmethod + async def _dup(self, download_only: bool) -> None: + pass + + async def dup(self, download_only: bool=False) -> None: + return await self._dup(download_only=download_only) + + # -- reboot_required + + @abc.abstractmethod + async def _reboot_required(self, verbose: bool) -> bool: + pass + + async def reboot_required(self, verbose: bool=False) -> bool: + return await self._reboot_required(verbose=verbose) + + # -- select + + @abc.abstractmethod + async def _select(self, names: Iterable[str]) -> Iterable[Package]: + pass + + async def select(self, names: Iterable[str] = []) -> Iterable[Package]: + return await self._select(names) + + # -- install + + @abc.abstractmethod + async def _install(self, names: Iterable[str], only_update: bool) -> None: + pass + + async def install(self, names: Iterable[str], only_update: bool=False) -> None: + return await self._install(names, only_update=only_update) + + # -- delete + + @abc.abstractmethod + async def _delete(self, names: Iterable[str]) -> None: + pass + + async def delete(self, names: Iterable[str]) -> None: + return await self._delete(names) + + # -- pkg_files + + @abc.abstractmethod + async def _pkg_files(self, name: str) -> Iterable[str]: + pass + + async def pkg_files(self, name: str) -> Iterable[str]: + return await self._pkg_files(name) diff --git a/src/python/jw/pkg/lib/ExecContext.py b/src/python/jw/pkg/lib/ExecContext.py new file mode 100644 index 00000000..f021b1c2 --- /dev/null +++ b/src/python/jw/pkg/lib/ExecContext.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +import abc +from typing import NamedTuple + +class Result(NamedTuple): + + stdout: str|None + stderr: str|None + status: int|None + +class ExecContext(abc.ABC): + + def __init__(self, interactive: bool=True): + self.__interactive = interactive + + @property + def interactive(self): + return self.__interactive + + @abc.abstractmethod + async def _run(self, *args, **kwargs) -> Result: + pass + + async def run(self, *args, **kwargs) -> Result: + return await self._run(*args, **kwargs) + + @abc.abstractmethod + async def _sudo(self, cmd: list[str], mod_env: dict[str, str] = {}, opts: list[str]=[], verbose=True) -> Result: + pass + + async def sudo(self, cmd: list[str], mod_env: dict[str, str] = {}, opts: list[str]=[], verbose=True) -> Result: + return await self._sudo(cmd, mod_env, opts, verbose) diff --git a/src/python/jw/pkg/lib/distros/Backend.py b/src/python/jw/pkg/lib/distros/Backend.py deleted file mode 100644 index c1632a10..00000000 --- a/src/python/jw/pkg/lib/distros/Backend.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import annotations -from typing import TYPE_CHECKING - -from ..util import run_sudo - -if TYPE_CHECKING: - from ..Cmd import Cmd - -class Backend: - - def __init__(self, parent: Cmd): - self.__parent = parent - - async def _sudo(self, *args, **kwargs): - return await run_sudo(*args, interactive=self.interactive, **kwargs) - - @property - def util(self): - return self.__parent.util - - @property - def parent(self): - return self.__parent - - @property - def interactive(self) -> bool: - return self.__parent.interactive diff --git a/src/python/jw/pkg/lib/distros/BeDelete.py b/src/python/jw/pkg/lib/distros/BeDelete.py deleted file mode 100644 index c592ef17..00000000 --- a/src/python/jw/pkg/lib/distros/BeDelete.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -import abc -from argparse import Namespace - -from .Backend import Backend as Base -from ..Cmd import Cmd as Parent - -class BeDelete(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - @abc.abstractmethod - async def run(self, args: Namespace) -> None: - pass diff --git a/src/python/jw/pkg/lib/distros/BeDup.py b/src/python/jw/pkg/lib/distros/BeDup.py deleted file mode 100644 index d91213e3..00000000 --- a/src/python/jw/pkg/lib/distros/BeDup.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -import abc -from argparse import Namespace - -from .Backend import Backend as Base -from ..Cmd import Cmd as Parent - -class BeDup(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - @abc.abstractmethod - async def run(self, args: Namespace) -> None: - pass diff --git a/src/python/jw/pkg/lib/distros/BeInstall.py b/src/python/jw/pkg/lib/distros/BeInstall.py deleted file mode 100644 index 6b4ec0d6..00000000 --- a/src/python/jw/pkg/lib/distros/BeInstall.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -import abc -from argparse import Namespace - -from .Backend import Backend as Base -from ..Cmd import Cmd as Parent - -class BeInstall(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - @abc.abstractmethod - async def run(self, args: Namespace) -> None: - pass diff --git a/src/python/jw/pkg/lib/distros/BePkg.py b/src/python/jw/pkg/lib/distros/BePkg.py deleted file mode 100644 index 66ffe84e..00000000 --- a/src/python/jw/pkg/lib/distros/BePkg.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import annotations -from typing import Iterable, TYPE_CHECKING - -import abc - -from ..Package import Package -from .Backend import Backend as Base - -if TYPE_CHECKING: - from ..Cmd import Cmd as Parent - -class BePkg(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - async def files(self, name: str) -> Iterable[str]: - return await self._files(name) - - @abc.abstractmethod - async def _files(self, name: str) -> Iterable[str]: - pass - - async def meta_data(self, names: Iterable[str]) -> Iterable[Package]: - return await self._meta_data(names) - - @abc.abstractmethod - async def _meta_data(self, names: Iterable[str]) -> Iterable[Package]: - pass diff --git a/src/python/jw/pkg/lib/distros/BeRebootRequired.py b/src/python/jw/pkg/lib/distros/BeRebootRequired.py deleted file mode 100644 index 14009593..00000000 --- a/src/python/jw/pkg/lib/distros/BeRebootRequired.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -import abc -from argparse import Namespace - -from .Backend import Backend as Base -from ..Cmd import Cmd as Parent - -class BeRebootRequired(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - @abc.abstractmethod - async def run(self, args: Namespace) -> None: - pass diff --git a/src/python/jw/pkg/lib/distros/BeRefresh.py b/src/python/jw/pkg/lib/distros/BeRefresh.py deleted file mode 100644 index 245cad38..00000000 --- a/src/python/jw/pkg/lib/distros/BeRefresh.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -import abc -from argparse import Namespace - -from .Backend import Backend as Base -from ..Cmd import Cmd as Parent - -class BeRefresh(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - @abc.abstractmethod - async def run(self, args: Namespace) -> None: - pass diff --git a/src/python/jw/pkg/lib/distros/BeSelect.py b/src/python/jw/pkg/lib/distros/BeSelect.py deleted file mode 100644 index 72cd5a26..00000000 --- a/src/python/jw/pkg/lib/distros/BeSelect.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import annotations -from typing import TYPE_CHECKING - -import abc -from typing import Iterable - -from .Backend import Backend as Base - -if TYPE_CHECKING: - from ..Cmd import Cmd as Parent - from .Package import Package - -class BeSelect(Base): - - def __init__(self, parent: Parent): - super().__init__(parent) - - @property - async def all_installed_packages(self) -> Iterable[Package]: - return await self._all_installed_packages() - - @abc.abstractmethod - async def _all_installed_packages(self) -> Iterable[Package]: - pass diff --git a/src/python/jw/pkg/lib/distros/Util.py b/src/python/jw/pkg/lib/distros/Util.py deleted file mode 100644 index 4a54b1c7..00000000 --- a/src/python/jw/pkg/lib/distros/Util.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import annotations -from typing import TYPE_CHECKING - -from ..util import run_sudo - -if TYPE_CHECKING: - from ..Cmd import Cmd - -class Util: - - def __init__(self, parent: Cmd): - self.__parent = parent - - async def _sudo(self, *args, **kwargs): - return await run_sudo(*args, interactive=self.interactive, **kwargs) - - @property - def parent(self): - return self.__parent - - @property - def interactive(self) -> bool: - return self.__parent.interactive diff --git a/src/python/jw/pkg/lib/distros/arch/Distro.py b/src/python/jw/pkg/lib/distros/arch/Distro.py new file mode 100644 index 00000000..bd5df671 --- /dev/null +++ b/src/python/jw/pkg/lib/distros/arch/Distro.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +from __future__ import annotations +from typing import TYPE_CHECKING + +from ...Distro import Distro as Base + +if TYPE_CHECKING: + from typing import Iterable + from ...ExecContext import Result + from ...Package import Package + +class Distro(Base): + + async def pacman(self, args: list[str]) -> Result: + cmd = ['/usr/bin/pacman'] + if not self.interactive: + cmd.extend(['--noconfirm']) + cmd.extend(args) + return await self.sudo(cmd) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def _ref(self) -> None: + raise NotImplementedError('distro refresh is not yet implemented for Arch-like distributions') + + async def _dup(self, download_only: bool) -> None: + args = ['-Su'] + if args.download_only: + args.append('-w') + return await self.util.pacman(args) + + async def _reboot_required(self, verbose: bool) -> bool: + raise NotImplementedError('distro reboot-required is not yet implemented for Arch-like distributions') + + async def _select(self, names: Iterable[str]) -> Iterable[Package]: + return await query_packages(names) + + async def _install(self, names: Iterable[str], only_update: bool) -> None: + if only_update: + raise NotImplementedError('--only-update is not yet implemented for pacman') + args = ['-S', '--needed'] + args.extend(args.packages) + await self.util.pacman(args) + + async def _delete(self, names: Iterable[str]) -> None: + raise NotImplementedError('distro delete not yet implemented for Arch-like distributions') + + async def _pkg_files(self, name: str) -> Iterable[str]: + raise NotImplementedError('distro pkg ls yet implemented for Arch-like distributions') diff --git a/src/python/jw/pkg/lib/distros/arch/Dup.py b/src/python/jw/pkg/lib/distros/arch/Dup.py deleted file mode 100644 index 87ea8063..00000000 --- a/src/python/jw/pkg/lib/distros/arch/Dup.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeDup import BeDup as Base - -class Dup(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - pm_args = ['-Su'] - if args.download_only: - pm_args.append('-w') - return await self.util.pacman(pm_args) diff --git a/src/python/jw/pkg/lib/distros/arch/Install.py b/src/python/jw/pkg/lib/distros/arch/Install.py deleted file mode 100644 index c309aa31..00000000 --- a/src/python/jw/pkg/lib/distros/arch/Install.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeInstall import BeInstall as Base - -class Install(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - if args.only_update: - raise NotImplementedError('--only-update is not yet implemented for pacman') - pacman_args = ['-S', '--needed'] - pacman_args.extend(args.packages) - await self.util.pacman(*pacman_args) diff --git a/src/python/jw/pkg/lib/distros/arch/Refresh.py b/src/python/jw/pkg/lib/distros/arch/Refresh.py deleted file mode 100644 index 849ff048..00000000 --- a/src/python/jw/pkg/lib/distros/arch/Refresh.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeRefresh import BeRefresh as Base - -class Refresh(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - raise NotImplementedError('distro refresh is not yet implemented for Arch-like distributions') diff --git a/src/python/jw/pkg/lib/distros/arch/Util.py b/src/python/jw/pkg/lib/distros/arch/Util.py deleted file mode 100644 index 1bf03f02..00000000 --- a/src/python/jw/pkg/lib/distros/arch/Util.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -from ...Cmd import Cmd -from ..Util import Util as Base - -class Util(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def pacman(self, *args): - cmd = ['/usr/bin/pacman'] - if not self.interactive: - cmd.extend(['--noconfirm']) - cmd.extend(args) - return await self._sudo(cmd) diff --git a/src/python/jw/pkg/lib/distros/debian/Delete.py b/src/python/jw/pkg/lib/distros/debian/Delete.py deleted file mode 100644 index e07503d0..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Delete.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeDelete import BeDelete as Base - -class Delete(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - return await self.util.dpkg(['-P', *args.names], sudo=True) diff --git a/src/python/jw/pkg/lib/distros/debian/Distro.py b/src/python/jw/pkg/lib/distros/debian/Distro.py new file mode 100644 index 00000000..2cbbac1c --- /dev/null +++ b/src/python/jw/pkg/lib/distros/debian/Distro.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from __future__ import annotations +from typing import TYPE_CHECKING + +import os + +from ...log import * +from ...Distro import Distro as Base +from ...pm.dpkg import run_dpkg, run_dpkg_query, query_packages, list_files + +if TYPE_CHECKING: + from typing import Iterable + from ...ExecContext import Result + from ...Package import Package + +class Distro(Base): + + async def apt_get(self, args: list[str]): + cmd = ['/usr/bin/apt-get'] + mod_env = None + if not self.interactive: + cmd.extend(['--yes', '--quiet']) + mod_env = { 'DEBIAN_FRONTEND': 'noninteractive' } + cmd.extend(args) + return await self.sudo(cmd, mod_env=mod_env) + + async def dpkg(self, *args, **kwargs): + return await run_dpkg(*args, **kwargs) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def _ref(self) -> None: + return await self.apt_get(['update']) + + async def _dup(self, download_only: bool) -> None: + args: list[str] = [] + if download_only: + args.append('--download-only') + args.append('upgrade') + return await self.apt_get(args) + + async def _reboot_required(self, verbose: bool) -> bool: + reboot_required = '/run/reboot_required' + if os.path.exists(reboot_required): + if verbose: + log(NOTICE, f'Yes. {reboot_required} exists.') + required_pkgs = '/run/reboot-required.pkgs' + if os.path.exists(required_pkgs): + with open(required_pkgs, 'r') as f: + content = f.read() + print(f'-- From {required_pkgs}:') + print(content.strip()) + return True + if verbose: + log(NOTICE, f'No. {reboot_required} doesn\'t exist.') + return False + + async def _select(self, names: Iterable[str]) -> Iterable[Package]: + return await query_packages(names) + + async def _install(self, names: Iterable[str], only_update: bool) -> None: + args = ['install'] + if only_update: + args.append('--only-upgrade') + args.append('--no-install-recommends') + args.extend(names) + return await self.apt_get(args) + + async def _delete(self, names: Iterable[str]) -> None: + return await self.dpkg(['-P', *names], sudo=True) + + async def _pkg_files(self, name: str) -> Iterable[str]: + return await list_files(name) diff --git a/src/python/jw/pkg/lib/distros/debian/Dup.py b/src/python/jw/pkg/lib/distros/debian/Dup.py deleted file mode 100644 index c921297c..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Dup.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeDup import BeDup as Base - -class Dup(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - apt_get_args: list[str] = [] - if args.download_only: - apt_get_args.append('--download-only') - apt_get_args.append('upgrade') - return await self.util.apt_get(apt_get_args) diff --git a/src/python/jw/pkg/lib/distros/debian/Install.py b/src/python/jw/pkg/lib/distros/debian/Install.py deleted file mode 100644 index eaa23dce..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Install.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeInstall import BeInstall as Base - -class Install(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - apt_get_args = ['install'] - if args.only_update: - apt_get_args.append('--only-upgrade') - apt_get_args.append('--no-install-recommends') - apt_get_args.extend(args.packages) - return await self.util.apt_get(apt_get_args) diff --git a/src/python/jw/pkg/lib/distros/debian/Pkg.py b/src/python/jw/pkg/lib/distros/debian/Pkg.py deleted file mode 100644 index a3ffc812..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Pkg.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Iterable -from argparse import Namespace - -from ...Package import Package -from ...pm.dpkg import list_files, query_packages -from ...Cmd import Cmd -from ..BePkg import BePkg as Base - -class Pkg(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def _files(self, name: str) -> Iterable[str]: - return await list_files(name) - - async def _meta_data(self, names: Iterable[str]) -> Iterable[Package]: - return await query_packages(names) diff --git a/src/python/jw/pkg/lib/distros/debian/RebootRequired.py b/src/python/jw/pkg/lib/distros/debian/RebootRequired.py deleted file mode 100644 index cda346a2..00000000 --- a/src/python/jw/pkg/lib/distros/debian/RebootRequired.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -from argparse import Namespace - -from ...log import * -from ...Cmd import Cmd -from ..BeRebootRequired import BeRebootRequired as Base - -class RebootRequired(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - reboot_required = '/run/reboot_required' - if os.path.exists(reboot_required): - if args.verbose: - log(NOTICE, f'Yes. {reboot_required} exists.') - required_pkgs = '/run/reboot-required.pkgs' - if os.path.exists(required_pkgs): - with open(required_pkgs, 'r') as f: - content = f.read() - print(f'-- From {required_pkgs}:') - print(content.strip()) - return 1 - if args.verbose: - log(NOTICE, f'No. {reboot_required} doesn\'t exist.') - return 0 diff --git a/src/python/jw/pkg/lib/distros/debian/Refresh.py b/src/python/jw/pkg/lib/distros/debian/Refresh.py deleted file mode 100644 index 1736e06b..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Refresh.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeRefresh import BeRefresh as Base - -class Refresh(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - return await self.util.apt_get(['update']) diff --git a/src/python/jw/pkg/lib/distros/debian/Select.py b/src/python/jw/pkg/lib/distros/debian/Select.py deleted file mode 100644 index 7db5f76f..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Select.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Iterable -from argparse import Namespace - -from ...Package import Package -from ...pm.dpkg import query_packages -from ...Cmd import Cmd -from ..BeSelect import BeSelect as Base - -class Select(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def _all_installed_packages(self) -> Iterable[Package]: - return await query_packages() diff --git a/src/python/jw/pkg/lib/distros/debian/Util.py b/src/python/jw/pkg/lib/distros/debian/Util.py deleted file mode 100644 index 3722363b..00000000 --- a/src/python/jw/pkg/lib/distros/debian/Util.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - -from ...Cmd import Cmd -from ...pm.dpkg import run_dpkg -from ..Util import Util as Base - -class Util(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def apt_get(self, args: list[str]): - cmd = ['/usr/bin/apt-get'] - mod_env = None - if not self.interactive: - cmd.extend(['--yes', '--quiet']) - mod_env = { 'DEBIAN_FRONTEND': 'noninteractive' } - cmd.extend(args) - return await self._sudo(cmd, mod_env=mod_env) - - async def dpkg(self, *args, **kwargs): - return await run_dpkg(*args, **kwargs) diff --git a/src/python/jw/pkg/lib/distros/redhat/Dup.py b/src/python/jw/pkg/lib/distros/redhat/Dup.py deleted file mode 100644 index 17f499f5..00000000 --- a/src/python/jw/pkg/lib/distros/redhat/Dup.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeDup import BeDup as Base - -class Dup(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - yum_args: list[str] = ['update'] - if args.download_only: - yum_args.append('--downloadonly') - return await self.yum(yum_args) diff --git a/src/python/jw/pkg/lib/distros/redhat/Install.py b/src/python/jw/pkg/lib/distros/redhat/Install.py deleted file mode 100644 index 74375db5..00000000 --- a/src/python/jw/pkg/lib/distros/redhat/Install.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeInstall import BeInstall as Base - -class Install(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - yum_args = ['update' if args.only_update else 'install'] - if not self.interactive: - yum_args.append['-y'] - yum_args.extend(args.packages) - return await self.util.yum(*yum_args) diff --git a/src/python/jw/pkg/lib/distros/redhat/Refresh.py b/src/python/jw/pkg/lib/distros/redhat/Refresh.py deleted file mode 100644 index 0bc930c9..00000000 --- a/src/python/jw/pkg/lib/distros/redhat/Refresh.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeRefresh import BeRefresh as Base - -class Refresh(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - await self.util.yum('clean', 'expire-cache') - await self.util.yum('makecache') diff --git a/src/python/jw/pkg/lib/distros/redhat/Util.py b/src/python/jw/pkg/lib/distros/redhat/Util.py deleted file mode 100644 index 1ffee88e..00000000 --- a/src/python/jw/pkg/lib/distros/redhat/Util.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from ...Cmd import Cmd -from ..Util import Util as Base - -class Util(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def yum(self, *args): - cmd = ['/usr/bin/yum'] - cmd.extend(args) - return await self._sudo(cmd) diff --git a/src/python/jw/pkg/lib/distros/suse/Delete.py b/src/python/jw/pkg/lib/distros/suse/Delete.py deleted file mode 100644 index 9be970f1..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Delete.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeDelete import BeDelete as Base - -class Delete(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - return await self.util.rpm(['-e', *args.names], sudo=True) diff --git a/src/python/jw/pkg/lib/distros/suse/Distro.py b/src/python/jw/pkg/lib/distros/suse/Distro.py new file mode 100644 index 00000000..51cc2e82 --- /dev/null +++ b/src/python/jw/pkg/lib/distros/suse/Distro.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from __future__ import annotations +from typing import TYPE_CHECKING + +from ...Distro import Distro as Base +from ...pm.rpm import run_rpm, query_packages, list_files + +if TYPE_CHECKING: + from typing import Iterable + from ...ExecContext import Result + from ...Package import Package + +class Distro(Base): + + async def zypper(self, args: list[str], verbose: bool=True, sudo: bool=True) -> Result: + cmd = ['/usr/bin/zypper'] + if not self.interactive: + cmd.extend(['--non-interactive', '--gpg-auto-import-keys', '--no-gpg-checks']) + cmd.extend(args) + if sudo: + # Run sudo --login in case /etc/profile modifies ZYPP_CONF + return await self.sudo(cmd, opts=['--login'], verbose=verbose) + return await self.run(cmd, verbose=verbose) + + async def rpm(self, *args, **kwargs) -> Result: + return await run_rpm(*args, **kwargs) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def _ref(self) -> None: + return await self.zypper(['refresh']) + + async def _dup(self, download_only: bool) -> None: + args = ['dup', '--force-resolution', '--auto-agree-with-licenses'] + if download_only: + args.append('--download-only') + return await self.zypper(args) + + async def _reboot_required(self, verbose: bool) -> bool: + opts = [] + if not verbose: + pass + #opts.append('--quiet') + opts.append('needs-rebooting') + stdout, stderr, ret = await self.zypper(opts, sudo=False, verbose=verbose) + if ret != 0: + return True + return False + + async def _select(self, names: Iterable[str]) -> Iterable[Package]: + return await query_packages(names) + + async def _install(self, names: Iterable[str], only_update: bool) -> None: + cmd = 'update' if only_update else 'install' + return await self.zypper([cmd, *names]) + + async def _delete(self, names: Iterable[str]) -> None: + return await self.rpm(['-e', *names], sudo=True) + + async def _pkg_files(self, name: str) -> Iterable[str]: + return await list_files(name) diff --git a/src/python/jw/pkg/lib/distros/suse/Dup.py b/src/python/jw/pkg/lib/distros/suse/Dup.py deleted file mode 100644 index 0e330f6a..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Dup.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeDup import BeDup as Base - -class Dup(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - zypper_args = ['dup', '--force-resolution', '--auto-agree-with-licenses'] - if args.download_only: - zypper_args.append('--download-only') - return await self.util.zypper(zypper_args) diff --git a/src/python/jw/pkg/lib/distros/suse/Install.py b/src/python/jw/pkg/lib/distros/suse/Install.py deleted file mode 100644 index ad4e70af..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Install.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeInstall import BeInstall as Base - -class Install(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - zypper_cmd = 'update' if args.only_update else 'install' - return await self.util.zypper([zypper_cmd, *args.packages]) diff --git a/src/python/jw/pkg/lib/distros/suse/Pkg.py b/src/python/jw/pkg/lib/distros/suse/Pkg.py deleted file mode 100644 index 32c9860f..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Pkg.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Iterable -from argparse import Namespace - -from ...Package import Package -from ...pm.rpm import list_files, query_packages -from ...Cmd import Cmd -from ..BePkg import BePkg as Base - -class Pkg(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def _files(self, name: str) -> Iterable[str]: - return await list_files(name) - - async def _meta_data(self, names: Iterable[str]) -> Iterable[Package]: - return await query_packages(names) diff --git a/src/python/jw/pkg/lib/distros/suse/RebootRequired.py b/src/python/jw/pkg/lib/distros/suse/RebootRequired.py deleted file mode 100644 index 8e8c5c82..00000000 --- a/src/python/jw/pkg/lib/distros/suse/RebootRequired.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeRebootRequired import BeRebootRequired as Base - -class RebootRequired(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - opts = [] - if not args.verbose: - pass - #opts.append('--quiet') - opts.append('needs-rebooting') - stdout, stderr, ret = await self.util.zypper(opts, sudo=False, verbose=args.verbose) - if ret != 0: - return 1 - return 0 diff --git a/src/python/jw/pkg/lib/distros/suse/Refresh.py b/src/python/jw/pkg/lib/distros/suse/Refresh.py deleted file mode 100644 index f92fe773..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Refresh.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from argparse import Namespace - -from ...Cmd import Cmd -from ..BeRefresh import BeRefresh as Base - -class Refresh(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def run(self, args: Namespace): - return await self.util.zypper(['refresh']) diff --git a/src/python/jw/pkg/lib/distros/suse/Select.py b/src/python/jw/pkg/lib/distros/suse/Select.py deleted file mode 100644 index f20f16b3..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Select.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Iterable -from argparse import Namespace - -from ...Package import Package -from ...pm.rpm import query_packages -from ...Cmd import Cmd -from ..BeSelect import BeSelect as Base - -class Select(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def _all_installed_packages(self) -> Iterable[Package]: - return await query_packages() diff --git a/src/python/jw/pkg/lib/distros/suse/Util.py b/src/python/jw/pkg/lib/distros/suse/Util.py deleted file mode 100644 index f2f140d0..00000000 --- a/src/python/jw/pkg/lib/distros/suse/Util.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -from ...util import run_cmd -from ...Cmd import Cmd -from ..Util import Util as Base - -from ...pm.rpm import run_rpm - -class Util(Base): - - def __init__(self, parent: Cmd): - super().__init__(parent) - - async def zypper(self, args: list[str], verbose: bool=False, sudo: bool=True): - cmd = ['/usr/bin/zypper'] - if not self.interactive: - cmd.extend(['--non-interactive', '--gpg-auto-import-keys', '--no-gpg-checks']) - cmd.extend(args) - if sudo: - # Run sudo --login in case /etc/profile modifies ZYPP_CONF - return await self._sudo(cmd, opts=['--login'], verbose=verbose) - return await run_cmd(cmd, verbose=verbose) - - async def rpm(self, *args, **kwargs): - return await run_rpm(*args, **kwargs) diff --git a/src/python/jw/pkg/lib/ec/Local.py b/src/python/jw/pkg/lib/ec/Local.py new file mode 100644 index 00000000..0ef46a93 --- /dev/null +++ b/src/python/jw/pkg/lib/ec/Local.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import os + +from ..util import run_cmd +from ..ExecContext import ExecContext as Base +from ..ExecContext import Result + +class Local(Base): + + async def _run(self, *args, **kwargs) -> Result: + return await run_cmd(*args, **kwargs) + + async def _sudo(self, cmd: list[str], mod_env: dict[str, str] = {}, opts: list[str]=[], verbose=True) -> Result: + env: dict[str, str]|None = None + cmd_input: str|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) + if self.interactive: + cmd_input = "mode:interactive" + return await self._run(cmdline, throw=True, verbose=verbose, env=env, cmd_input=cmd_input) diff --git a/src/python/jw/pkg/lib/ec/Makefile b/src/python/jw/pkg/lib/ec/Makefile new file mode 100644 index 00000000..7a83c333 --- /dev/null +++ b/src/python/jw/pkg/lib/ec/Makefile @@ -0,0 +1,4 @@ +TOPDIR = ../../../../../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-mod.mk diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index 6d97fdb1..e164c3dc 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -69,9 +69,9 @@ async def run_cmd( if code == 0: return if (throw or verbose): - msg = f'Command returned error {code}: {pretty_cmd(args, wd)}: ' + msg = f'Command returned error {code}: {pretty_cmd(args, wd)}' if stderr: - msg += stderr.strip() + msg += ': ' + stderr.strip() if throw: raise RuntimeError(msg) @@ -279,21 +279,9 @@ async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=No return None async def run_sudo(cmd: list[str], mod_env: dict[str, str] = {}, opts: list[str]=[], interactive: bool=True, verbose=True): - env: dict[str, str]|None = None - cmd_input: str|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) - if interactive: - cmd_input = "mode:interactive" - return await run_cmd(cmdline, throw=True, verbose=verbose, env=env, cmd_input=cmd_input) + from .ec.Local import Local + ec = Local() + return await ec.sudo(cmd, mod_env, opts, interactive, verbose) 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