dnf/0014-bootc-Use-ostree-GObject-API-to-get-deployment-statu.patch
Petr Písař 7e6ee04c1e Add support for transient transactions
Resolves: RHEL-76849
2025-02-07 10:57:37 +01:00

167 lines
6.7 KiB
Diff

From c0f2329c2ed71f373aad26c7f1786494f6e75b76 Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Wed, 15 Jan 2025 21:43:58 +0000
Subject: [PATCH 14/17] bootc: Use ostree GObject API to get deployment status
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: f3abee56452e40ce475f714666c3a16426759c96
Using libostree gives us more detail about the current state of the
deployment than only checking whether /usr is writable.
Resolves: https://issues.redhat.com/browse/RHEL-76849
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 13 ++++----
dnf/util.py | 80 +++++++++++++++++++++++++++++++++++---------------
2 files changed, 65 insertions(+), 28 deletions(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 53f2d9d50..bc11505fc 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -218,18 +218,22 @@ class BaseCli(dnf.Base):
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
- is_bootc_transaction = dnf.util._is_bootc_host() and \
+ is_bootc_transaction = dnf.util._Bootc.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.
+ bootc = None
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():
+
+ bootc = dnf.util._Bootc()
+
+ if not bootc.is_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 "
@@ -239,7 +243,6 @@ class BaseCli(dnf.Base):
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."))
@@ -247,8 +250,8 @@ class BaseCli(dnf.Base):
if self.conf.assumeno or not self.output.userconfirm():
raise CliError(_("Operation aborted."))
- if bootc_unlock_requested:
- dnf.util._bootc_unlock()
+ if bootc:
+ bootc.unlock_and_prepare()
else:
logger.info(_('Nothing to do.'))
return
diff --git a/dnf/util.py b/dnf/util.py
index 2e270890c..51f853d8b 100644
--- a/dnf/util.py
+++ b/dnf/util.py
@@ -642,33 +642,67 @@ def _is_file_pattern_present(specs):
return False
-def _is_bootc_host():
- """Returns true is the system is managed as an immutable container, false
- otherwise."""
- ostree_booted = "/run/ostree-booted"
- return os.path.isfile(ostree_booted)
+class _Bootc:
+ usr = "/usr"
+ def __init__(self):
+ if not self.is_bootc_host():
+ raise RuntimeError(_("Not running on a bootc system."))
-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)
+ import gi
+ self._gi = gi
+ gi.require_version("OSTree", "1.0")
+ from gi.repository import OSTree
-def _bootc_unlock():
- """Set up a writeable overlay on bootc systems."""
+ self._OSTree = OSTree
- if _is_bootc_unlocked():
- return
+ self._sysroot = self._OSTree.Sysroot.new_default()
+ assert self._sysroot.load(None)
- unlock_command = ["bootc", "usr-overlay"]
+ self._booted_deployment = self._sysroot.require_booted_deployment()
+ assert self._booted_deployment is not None
- 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))
+ @staticmethod
+ def is_bootc_host():
+ """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 _get_unlocked_state(self):
+ return self._booted_deployment.get_unlocked()
+
+ def is_unlocked(self):
+ return self._get_unlocked_state() != self._OSTree.DeploymentUnlockedState.NONE
+
+ def unlock_and_prepare(self):
+ """Set up a writeable overlay on bootc systems."""
+
+ bootc_unlocked_state = self._get_unlocked_state()
+
+ valid_bootc_unlocked_states = (
+ self._OSTree.DeploymentUnlockedState.NONE,
+ self._OSTree.DeploymentUnlockedState.DEVELOPMENT,
+ # self._OSTree.DeploymentUnlockedState.TRANSIENT,
+ # self._OSTree.DeploymentUnlockedState.HOTFIX,
+ )
+
+ if bootc_unlocked_state not in valid_bootc_unlocked_states:
+ raise ValueError(_("Unhandled bootc unlocked state: %s") % bootc_unlocked_state.value_nick)
+
+ if bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.DEVELOPMENT:
+ # System is already unlocked.
+ pass
+ elif bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.NONE:
+ unlock_command = ["ostree", "admin", "unlock"]
+
+ try:
+ completed_process = subprocess.run(unlock_command, text=True)
+ completed_process.check_returncode()
+ except FileNotFoundError:
+ raise dnf.exceptions.Error(_("ostree command not found. Is this a bootc system?"))
+ except subprocess.CalledProcessError:
+ raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
+
+ assert os.access(self.usr, os.W_OK)
--
2.48.1