diff --git a/CMakeLists.txt b/CMakeLists.txt index a54e2c6e..fcb6364b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,9 @@ ELSE () MESSAGE (FATAL_ERROR "Invalid PYTHON_DESIRED value: " ${PYTHON_DESIRED}) ENDIF() -EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c "from sys import stdout; from sysconfig import get_path; stdout.write(get_path('purelib'))" OUTPUT_VARIABLE PYTHON_INSTALL_DIR) +IF (NOT PYTHON_INSTALL_DIR) + EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c "from sys import stdout; from sysconfig import get_path; stdout.write(get_path('purelib'))" OUTPUT_VARIABLE PYTHON_INSTALL_DIR) +ENDIF() MESSAGE(STATUS "Python install dir is ${PYTHON_INSTALL_DIR}") SET (SYSCONFDIR /etc) diff --git a/doc/bootc.rst b/doc/bootc.rst index 7e386d8e..aefda66e 100644 --- a/doc/bootc.rst +++ b/doc/bootc.rst @@ -8,7 +8,7 @@ Manipulate image mode based systems using RPMs available from dnf repositories. Synopsis -------- -``dnf bootc [status] `` +``dnf bootc [status|install] `` ----------------- Arguments (bootc) @@ -17,6 +17,9 @@ Arguments (bootc) ``bootc status `` The status of the image mode system. +``bootc install [options] ...`` + Install one or more packages. + ------- Options ------- @@ -33,7 +36,28 @@ All general DNF options are accepted, see `Options` in :manpage:`dnf(8)` for det Filter JSONPath expression (valid on the status subcommand). ``--pending-exit-77`` - If pending deploymewnt available, exit 77 (valid on the status subcommand). + If pending deployment available, exit 77 (valid on the status subcommand). + +``--uninstall PKG...`` + Remove overlayed additional package (valid on the install subcommand). + +``-A``, ``--apply-live`` + Apply changes to both pending deployment and running filesystem tree (valid on the install subcommand). + +``--force-replacefiles`` + Allow package to replace files from other packages (valid on the install subcommand). + +``-r``, ``--reboot`` + Initiate a reboot after operation is complete (valid on the install subcommand). + +``--allow-inactive`` + Allow inactive package requests (valid on the install subcommand). + +``--idempotent`` + Do nothing if package already (un)installed (valid on the install subcommand). + +``--unchanged-exit-77`` + If no overlays were changed, exit 77 (valid on the install subcommand). ``--peer`` - Force a peer-to-peer connection instead of using the system message bus (valid on the status subcommand). + Force a peer-to-peer connection instead of using the system message bus (valid on the status and install subcommands). diff --git a/doc/needs_restarting.rst b/doc/needs_restarting.rst index be3cc03c..e1fae48e 100644 --- a/doc/needs_restarting.rst +++ b/doc/needs_restarting.rst @@ -72,6 +72,9 @@ All general DNF options are accepted, see `Options` in :manpage:`dnf(8)` for det ``-s, --services`` Only list the affected systemd services. +``--exclude-services`` + Don't list stale processes that correspond to a systemd service. + ------------- Configuration ------------- diff --git a/plugins/bootc.py b/plugins/bootc.py index 055e6a6a..b9fc2670 100644 --- a/plugins/bootc.py +++ b/plugins/bootc.py @@ -19,19 +19,15 @@ class BootcCommand(dnf.cli.Command): aliases = ["bootc"] summary = _("Modify software on a bootc-based system") + usage = _("[PACKAGE ...]") _BOOTC_ALIASES = {"update": "upgrade", "erase": "remove"} - _BOOTC_SUBCOMMANDS = ["status", "install"] + _BOOTC_SUBCMDS = ["status"] + _BOOTC_SUBCMDS_PKGSPECS = ["install"] + _BOOTC_SUBCMDS_ALL = _BOOTC_SUBCMDS + _BOOTC_SUBCMDS_PKGSPECS _EXT_CMD = "rpm-ostree" - def _canonical(self): - if self.opts.subcmd is None: - self.opts.subcmd = "status" - - if self.opts.subcmd in self._BOOTC_ALIASES.keys(): - self.opts.subcmd = self._BOOTC_ALIASES[self.opts.subcmd] - def __init__(self, cli): super().__init__(cli) @@ -40,12 +36,9 @@ def set_argparser(parser): # subcommands for the plugin parser.add_argument( "subcmd", - nargs="?", - metavar="BOOTC", - help=_("available subcommands: {} (default), {}").format( - BootcCommand._BOOTC_SUBCOMMANDS[0], - ", ".join(BootcCommand._BOOTC_SUBCOMMANDS[1:]), - ), + nargs=1, + choices=BootcCommand._BOOTC_SUBCMDS_ALL, + help=_("Available subcommands"), ) # these options are for 'status' @@ -68,27 +61,70 @@ def set_argparser(parser): action="store_true", help=_("If pending deployment available, exit 77 (status)"), ) + + # these options are for 'install' + parser.add_argument( + "--uninstall", + nargs="+", + metavar="PKG", + action="store", + help=_("Remove overlayed additional package (install)"), + ) + parser.add_argument( + "-A", + "--apply-live", + action="store_true", + help=_( + "Apply changes to both pending deployment and running filesystem tree (install)" + ), + ) + parser.add_argument( + "--force-replacefiles", + action="store_true", + help=_("Allow package to replace files from other packages (install)"), + ) + parser.add_argument( + "-r", + "--reboot", + action="store_true", + help=_("Initiate a reboot after operation is complete (install)"), + ) + parser.add_argument( + "--allow-inactive", + action="store_true", + help=_("Allow inactive package requests (install)"), + ) + parser.add_argument( + "--idempotent", + action="store_true", + help=_("Do nothing if package already (un)installed (install)"), + ) + parser.add_argument( + "--unchanged-exit-77", + action="store_true", + help=_("If no overlays were changed, exit 77 (install)"), + ) + + # valid under multiple subcommands parser.add_argument( "--peer", action="store_true", help=_( - "Force a peer-to-peer connection instead of using the system message bus (status)" + "Force a peer-to-peer connection instead of using the system message bus (status, install)" ), ) + parser.add_argument( + "pkgspec", nargs="*", help=_("One or more package specifications") + ) + def configure(self): super().configure() - self._canonical() - cmd = self.opts.subcmd - - # ensure we have a valid subcommand - if cmd not in self._BOOTC_SUBCOMMANDS: - logger.critical( - _("Invalid bootc sub-command, use: %s."), - ", ".join(self._BOOTC_SUBCOMMANDS), - ) - raise dnf.cli.CliError + if self.opts.subcmd[0] in self._BOOTC_ALIASES.keys(): + cmd = self._BOOTC_ALIASES[self.opts.subcmd[0]] + else: + cmd = self.opts.subcmd[0] self.extargs = [self._EXT_CMD, cmd] @@ -110,6 +146,66 @@ def configure(self): self.extargs.append("--peer") elif self.opts.installroot: self.extargs.append("--sysroot=%s" % self.opts.installroot) + elif cmd == "install": + if self.opts.quiet: + self.extargs.append("-q") + elif self.opts.installroot: + self.extargs.append("--sysroot=%s" % self.opts.installroot) + elif self.opts.peer: + self.extargs.append("--peer") + elif self.opts.assumeyes: + self.extargs.append("-y") + elif self.opts.assumeno: + self.extargs.append("-n") + elif self.opts.cacheonly: + self.extargs.append("-C") + elif self.opts.downloadonly: + self.extargs.append("--download-only") + elif self.opts.releasever: + self.extargs.append + self.extargs.append("--releasever=%s" % self.opts.releasever) + elif len(self.opts.repos_ed) > 0: + enabled = set() + disabled = set() + + for name, state in self.opts.repos_ed: + if state == "enable": + enabled.add(name) + elif state == "disable": + disabled.add(name) + + if len(list(enabled)) > 0: + for repo in list(enabled): + self.extargs.append("--enablerepo=%s" % repo) + + if len(list(disabled)) > 0: + for repo in list(disabled): + self.extargs.append("--disablerepo=%s" % repo) + elif self.opts.uninstall: + for pname in self.opts.uninstall: + self.extargs.append("--uninstall=%s" % pname) + elif self.opts.apply_live: + self.extargs.append("-A") + elif self.opts.force_replacefiles: + self.extargs.append("--force-replacefiles") + elif self.opts.reboot: + self.extargs.append("-r") + elif self.opts.allow_inactive: + self.extargs.append("--allow-inactive") + elif self.opts.idempotent: + self.extargs.append("--idempotent") + elif self.opts.unchanged_exit_77: + self.extargs.append("--unchanged-exit-77") + + if cmd in self._BOOTC_SUBCMDS_PKGSPECS: + if self.opts.pkgspec is not None and len(self.opts.pkgspec) > 0: + self.extargs += self.opts.pkgspec + else: + # ensure we have a valid subcommand + logger.critical( + _("Missing package specification on bootc sub-command '%s'." % cmd) + ) + raise dnf.cli.CliError def run(self): # combine stdout and stderr; capture text output diff --git a/plugins/copr.py b/plugins/copr.py index afb83243..87319a72 100644 --- a/plugins/copr.py +++ b/plugins/copr.py @@ -487,6 +487,8 @@ def _guess_chroot(self): chroot = ("opensuse-tumbleweed-{}".format(distarch)) else: chroot = ("opensuse-leap-{0}-{1}".format(dist[1], distarch)) + elif "Amazon Linux" in dist[0]: + chroot = "amazonlinux-{}-{}".format(dist[1], distarch if distarch else "x86_64") else: chroot = ("epel-{}-{}".format(dist[1].split(".", 1)[0], distarch if distarch else "x86_64")) return chroot diff --git a/plugins/needs_restarting.py b/plugins/needs_restarting.py index 15127675..886b4df7 100644 --- a/plugins/needs_restarting.py +++ b/plugins/needs_restarting.py @@ -264,6 +264,8 @@ def set_argparser(parser): "(exit code 1) or not (exit code 0)")) parser.add_argument('-s', '--services', action='store_true', help=_("only report affected systemd services")) + parser.add_argument('--exclude-services', action='store_true', + help=_("don't list stale processes that correspond to a systemd service")) def configure(self): demands = self.cli.demands @@ -303,19 +305,32 @@ def run(self): return None stale_pids = set() + stale_service_names = set() uid = os.geteuid() if self.opts.useronly else None for ofile in list_opened_files(uid): pkg = owning_pkg_fn(ofile.presumed_name) + pid = ofile.pid if pkg is None: continue - if pkg.installtime > process_start(ofile.pid): - stale_pids.add(ofile.pid) + if pkg.installtime <= process_start(pid): + continue + if self.opts.services or self.opts.exclude_services: + service_name = get_service_dbus(pid) + if service_name is None: + stale_pids.add(pid) + else: + stale_service_names.add(service_name) + if not self.opts.exclude_services: + stale_pids.add(pid) + else: + # If neither --services nor --exclude-services is set, don't + # query D-Bus at all. + stale_pids.add(pid) if self.opts.services: - names = set([get_service_dbus(pid) for pid in sorted(stale_pids)]) - for name in names: - if name is not None: - print(name) + for stale_service_name in sorted(stale_service_names): + print(stale_service_name) return 0 + for pid in sorted(stale_pids): print_cmd(pid)