Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bootc: Add support for --transient #2155

Open
wants to merge 1 commit into
base: bootc
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions dnf/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,28 +205,50 @@ def do_transaction(self, display=()):
else:
self.output.reportDownloadSize(install_pkgs, install_only)

bootc_unlock_requested = False

if trans or self._moduleContainer.isChanged() or \
(self._history and (self._history.group or self._history.env)):
# confirm with user
if self.conf.downloadonly:
logger.info(_("{prog} will only download packages for the transaction.").format(
prog=dnf.util.MAIN_PROG_UPPER))

elif 'test' in self.conf.tsflags:
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
if dnf.util._is_bootc_host() and \
os.path.realpath(self.conf.installroot) == "/" and \
not self.conf.downloadonly:
_bootc_host_msg = _("""
*** Error: system is configured to be read-only; for more
*** information run `bootc --help`.
""")
logger.info(_bootc_host_msg)
raise CliError(_("Operation aborted."))

is_bootc_transaction = dnf.util._is_bootc_host() and \
os.path.realpath(self.conf.installroot) == "/" and \
not self.conf.downloadonly

# Handle bootc transactions. `--transient` must be specified if
# /usr is not already writeable.
if is_bootc_transaction:
if self.conf.persistence == "persist":
logger.info(_("Persistent transactions aren't supported on bootc systems."))
raise CliError(_("Operation aborted."))
assert self.conf.persistence in ("auto", "transient")
if not dnf.util._is_bootc_unlocked():
if self.conf.persistence == "auto":
logger.info(_("This bootc system is configured to be read-only. Pass --transient to "
"perform this and subsequent transactions in a transient overlay which "
"will reset when the system reboots."))
raise CliError(_("Operation aborted."))
assert self.conf.persistence == "transient"
logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. "
"Keep in mind that changes to /etc and /var will still persist, and packages "
"commonly modify these directories."))
bootc_unlock_requested = True
elif self.conf.persistence == "transient":
raise CliError(_("Transient transactions are only supported on bootc systems."))

if self._promptWanted():
if self.conf.assumeno or not self.output.userconfirm():
raise CliError(_("Operation aborted."))

if bootc_unlock_requested:
dnf.util._bootc_unlock()
else:
logger.info(_('Nothing to do.'))
return
Expand Down
3 changes: 3 additions & 0 deletions dnf/cli/option_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ def _add_general_options(self):
general_grp.add_argument("--downloadonly", dest="downloadonly",
action="store_true", default=False,
help=_("only download packages"))
general_grp.add_argument("--transient", dest="persistence",
action="store_const", const="transient", default=None,
help=_("Use a transient overlay which will reset on reboot"))
general_grp.add_argument("--comment", dest="comment", default=None,
help=_("add a comment to transaction"))
# Updateinfo options...
Expand Down
2 changes: 1 addition & 1 deletion dnf/conf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ def _configure_from_options(self, opts):
'best', 'assumeyes', 'assumeno', 'clean_requirements_on_remove', 'gpgcheck',
'showdupesfromrepos', 'plugins', 'ip_resolve',
'rpmverbosity', 'disable_excludes', 'color',
'downloadonly', 'exclude', 'excludepkgs', 'skip_broken',
'downloadonly', 'persistence', 'exclude', 'excludepkgs', 'skip_broken',
'tsflags', 'arch', 'basearch', 'ignorearch', 'cacheonly', 'comment']

for name in config_args:
Expand Down
40 changes: 29 additions & 11 deletions dnf/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import os
import pwd
import shutil
import subprocess
import sys
import tempfile
import time
Expand Down Expand Up @@ -642,15 +643,32 @@ def _is_file_pattern_present(specs):


def _is_bootc_host():
"""Returns true is the system is managed as an immutable container,
false otherwise. If msg is True, a warning message is displayed
for the user.
"""
ostree_booted = '/run/ostree-booted'
usr = '/usr/'
# Check if usr is writtable and we are in a running ostree system.
# We want this code to return true only when the system is in locked state. If someone ran
# bootc overlay or ostree admin unlock we would want normal DNF path to be ran as it will be
# temporary changes (until reboot).
return os.path.isfile(ostree_booted) and not os.access(usr, os.W_OK)
"""Returns true is the system is managed as an immutable container, false
otherwise."""
ostree_booted = "/run/ostree-booted"
return os.path.isfile(ostree_booted)


def _is_bootc_unlocked():
"""Check whether /usr is writeable, e.g. if we are in a normal mutable
system or if we are in a bootc after `bootc usr-overlay` or `ostree admin
unlock` was run."""
usr = "/usr"
return os.access(usr, os.W_OK)


def _bootc_unlock():
"""Set up a writeable overlay on bootc systems."""

if _is_bootc_unlocked():
return

unlock_command = ["bootc", "usr-overlay"]

try:
completed_process = subprocess.run(unlock_command, text=True)
completed_process.check_returncode()
except FileNotFoundError:
raise dnf.exceptions.Error(_("bootc command not found. Is this a bootc system?"))
except subprocess.CalledProcessError:
raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
Loading