Skip to content

Commit 6091f3f

Browse files
evan-goodeppisar
authored andcommitted
Add support for --transient
Adds support for the --transient option on all transactions. Passing --transient on a bootc system will call `bootc usr-overlay` to create a transient writeable /usr and continue the transaction. Specifying --transient on a non-bootc system will throw an error; we don't want to mislead users to thinking this feature works on non-bootc systems. If --transient is not specified and the bootc system is in a locked state, the operation will be aborted and a message will be printed suggesting to try again with --transient.
1 parent d3fab0f commit 6091f3f

File tree

4 files changed

+64
-21
lines changed

4 files changed

+64
-21
lines changed

dnf/cli/cli.py

+31-9
Original file line numberDiff line numberDiff line change
@@ -205,28 +205,50 @@ def do_transaction(self, display=()):
205205
else:
206206
self.output.reportDownloadSize(install_pkgs, install_only)
207207

208+
bootc_unlock_requested = False
209+
208210
if trans or self._moduleContainer.isChanged() or \
209211
(self._history and (self._history.group or self._history.env)):
210212
# confirm with user
211213
if self.conf.downloadonly:
212214
logger.info(_("{prog} will only download packages for the transaction.").format(
213215
prog=dnf.util.MAIN_PROG_UPPER))
216+
214217
elif 'test' in self.conf.tsflags:
215218
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
216219
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
217-
if dnf.util._is_bootc_host() and \
218-
os.path.realpath(self.conf.installroot) == "/" and \
219-
not self.conf.downloadonly:
220-
_bootc_host_msg = _("""
221-
*** Error: system is configured to be read-only; for more
222-
*** information run `bootc --help`.
223-
""")
224-
logger.info(_bootc_host_msg)
225-
raise CliError(_("Operation aborted."))
220+
221+
is_bootc_transaction = dnf.util._is_bootc_host() and \
222+
os.path.realpath(self.conf.installroot) == "/" and \
223+
not self.conf.downloadonly
224+
225+
# Handle bootc transactions. `--transient` must be specified if
226+
# /usr is not already writeable.
227+
if is_bootc_transaction:
228+
if self.conf.persistence == "persist":
229+
logger.info(_("Persistent transactions aren't supported on bootc systems."))
230+
raise CliError(_("Operation aborted."))
231+
assert self.conf.persistence in ("auto", "transient")
232+
if not dnf.util._is_bootc_unlocked():
233+
if self.conf.persistence == "auto":
234+
logger.info(_("This bootc system is configured to be read-only. Pass --transient to "
235+
"perform this and subsequent transactions in a transient overlay which "
236+
"will reset when the system reboots."))
237+
raise CliError(_("Operation aborted."))
238+
assert self.conf.persistence == "transient"
239+
logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. "
240+
"Keep in mind that changes to /etc and /var will still persist, and packages "
241+
"commonly modify these directories."))
242+
bootc_unlock_requested = True
243+
elif self.conf.persistence == "transient":
244+
raise CliError(_("Transient transactions are only supported on bootc systems."))
226245

227246
if self._promptWanted():
228247
if self.conf.assumeno or not self.output.userconfirm():
229248
raise CliError(_("Operation aborted."))
249+
250+
if bootc_unlock_requested:
251+
dnf.util._bootc_unlock()
230252
else:
231253
logger.info(_('Nothing to do.'))
232254
return

dnf/cli/option_parser.py

+3
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ def _add_general_options(self):
317317
general_grp.add_argument("--downloadonly", dest="downloadonly",
318318
action="store_true", default=False,
319319
help=_("only download packages"))
320+
general_grp.add_argument("--transient", dest="persistence",
321+
action="store_const", const="transient", default=None,
322+
help=_("Use a transient overlay which will reset on reboot"))
320323
general_grp.add_argument("--comment", dest="comment", default=None,
321324
help=_("add a comment to transaction"))
322325
# Updateinfo options...

dnf/conf/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ def _configure_from_options(self, opts):
343343
'best', 'assumeyes', 'assumeno', 'clean_requirements_on_remove', 'gpgcheck',
344344
'showdupesfromrepos', 'plugins', 'ip_resolve',
345345
'rpmverbosity', 'disable_excludes', 'color',
346-
'downloadonly', 'exclude', 'excludepkgs', 'skip_broken',
346+
'downloadonly', 'persistence', 'exclude', 'excludepkgs', 'skip_broken',
347347
'tsflags', 'arch', 'basearch', 'ignorearch', 'cacheonly', 'comment']
348348

349349
for name in config_args:

dnf/util.py

+29-11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import os
3939
import pwd
4040
import shutil
41+
import subprocess
4142
import sys
4243
import tempfile
4344
import time
@@ -642,15 +643,32 @@ def _is_file_pattern_present(specs):
642643

643644

644645
def _is_bootc_host():
645-
"""Returns true is the system is managed as an immutable container,
646-
false otherwise. If msg is True, a warning message is displayed
647-
for the user.
648-
"""
649-
ostree_booted = '/run/ostree-booted'
650-
usr = '/usr/'
651-
# Check if usr is writtable and we are in a running ostree system.
652-
# We want this code to return true only when the system is in locked state. If someone ran
653-
# bootc overlay or ostree admin unlock we would want normal DNF path to be ran as it will be
654-
# temporary changes (until reboot).
655-
return os.path.isfile(ostree_booted) and not os.access(usr, os.W_OK)
646+
"""Returns true is the system is managed as an immutable container, false
647+
otherwise."""
648+
ostree_booted = "/run/ostree-booted"
649+
return os.path.isfile(ostree_booted)
650+
651+
652+
def _is_bootc_unlocked():
653+
"""Check whether /usr is writeable, e.g. if we are in a normal mutable
654+
system or if we are in a bootc after `bootc usr-overlay` or `ostree admin
655+
unlock` was run."""
656+
usr = "/usr"
657+
return os.access(usr, os.W_OK)
658+
656659

660+
def _bootc_unlock():
661+
"""Set up a writeable overlay on bootc systems."""
662+
663+
if _is_bootc_unlocked():
664+
return
665+
666+
unlock_command = ["bootc", "usr-overlay"]
667+
668+
try:
669+
completed_process = subprocess.run(unlock_command, text=True)
670+
completed_process.check_returncode()
671+
except FileNotFoundError:
672+
raise dnf.exceptions.Error(_("bootc command not found. Is this a bootc system?"))
673+
except subprocess.CalledProcessError:
674+
raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))

0 commit comments

Comments
 (0)