Files
apt-ostree/apt_ostree/rebase.py
Charles Short 8a70aad74b Add support "rebase" subcommand
The rebase commands merges Debian packages from one branch
to another. The way that this works is the following:

1. Determine the current deployment of a running system.
2. Checkout the current deployment from an ostree repo.
3. Determine the packages installed in the current deployment.
4. Checkout the target branch to deploy.
5. Determine the packages installed in the target branch.
6. Determine the delta of the old packages that is not installed
   in the target branch.
7. Install the packakge delta in the target branch.
8. Commit the changes.
9. Deploy the new branch.

Story: 2010867
Task: 48556

Change-Id: Ibd302712c3131c64978df50a32d3b1d075eac0e3
Signed-off-by: Charles Short <charles.short@windriver.com>
2023-10-30 09:52:24 -04:00

159 lines
5.6 KiB
Python

"""
Copyright (c) 2023 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import logging
import sys
from rich.console import Console
from apt_ostree.apt import Apt
from apt_ostree.deploy import Deploy
from apt_ostree.ostree import Ostree
class Rebase:
def __init__(self, state):
self.state = state
self.console = Console()
self.ostree = Ostree(self.state)
self.deploy = Deploy(self.state)
self.apt = Apt(self.state)
self.logging = logging.getLogger(__name__)
def rebase(self, update):
"""Switch to another branch."""
# Verify the branch is formatted correctly.
try:
(remote, branch) = self.state.branch.split(":")
except KeyError:
self.logging.error(
"Branch must be in the format of <remote>:<branch>"
)
sys.exit(1)
# Make sure that we have remotes configured.
if len(self._get_remotes()) == 0:
self.logging.error("No remotes configured.")
sys.exit(1)
if update:
self.logging.info(f"Pulling {branch} from {remote}.")
with self.console.status(f"Fetching {branch} from {remote}"):
self._fetch(remote, branch)
sys.exit(1)
else:
# Get the current deployment, check for
# a deployed sysroot.
sysroot = self.ostree.get_sysroot()
if sysroot is None:
self.logging.error("Not running ostree.")
sys.exit(1)
csum = sysroot.get_booted_deployment().get_csum()
origin = sysroot.get_booted_deployment().get_origin()
refspec = origin.get_string('origin', 'refspec')
if refspec is None:
self.logging.error("Unable to determine branch.")
sys.exit(1)
self.logging.info(f"Rebasing {refspec} on to {self.state.branch}.")
self.logging.info(f"Local deployment: {refspec} ({csum[:10]}).")
# Get a list of manually installed packages.
# These include packages that were installed after an
# initial boostrap as well.
current_packages = self.get_current_packages(refspec, csum)
# If we didnt fetch the latest repository do it now.
self.logging.info(f"Pulling {branch} from {remote}.")
self._fetch(remote, branch)
# Prepare the new branch for deployment.
ref = self.ostree.ostree_ref(self.state.branch)
if ref is None:
self.logging.error(f"Failed to fetch {self.state.branch}")
sys.exit(1)
self.logging.debug(f"Fetched {self.state.branch} ({ref[:10]})")
# Rebase onto the pristine branch.
rootfs = self.deploy.get_sysroot(self.state.branch)
if rootfs is None:
self.logging.error(f"Unable to checkout {self.state.branch}")
sys.exit(1)
# Prestaging
self.deploy.prestaging(rootfs)
self.apt.apt_update(rootfs)
cache = self.apt.cache(rootfs)
new_packages = self.apt.get_installed_packages(cache)
# Determine packages that are missing and install them.
self.logging.debug(
f"Deteriming package delta between {refspec} "
f"and {self.state.branch}."
)
packages = list(current_packages - new_packages)
if len(packages) != 0:
commit = f"Resynchronize {self.state.branch} ({ref[:10]})"\
f" with {refspec} ({csum[:10]}).\n\n"
subject = "Resynchronize package list"
for pkg in packages:
version = self.apt.get_version(cache, pkg)
commit += f" - {pkg} {version}"
self.logging.info(f"Installing {pkg} ({version}).")
self.apt.apt_install(cache, packages, rootfs)
else:
subject = f"Updated {origin}"
commit = "Previous deployment: {csum[:10]}"
self.deploy.poststaging(rootfs)
self.logging.info(f"Commiting to {self.state.branch}.")
self.ostree.ostree_commit(
root=str(rootfs),
branch=self.state.branch,
repo=self.state.repo,
subject=subject,
msg=commit
)
self.deploy.cleanup(rootfs)
def _get_remotes(self):
"""List of remotes configured."""
return [
refs for refs in self.ostree.remotes_list()
if refs != "origin"]
def _fetch(self, remote, branch):
"""Wrapper around ostree fertch."""
self.ostree.fetch(remote, branch)
def get_current_packages(self, branch, ref):
"""Steps to prepare the systeem before a rebase."""
self.logging.debug(
f"Deploying current deployment {branch} ({ref[:10]})")
rootfs = self.deploy.get_sysroot(branch)
if not rootfs.exists():
self.logging.error("Unable to determine rootfs: {rootfs}")
sys.exit(1)
# Deploy the current booted deployment and run apt-update
# to configure apt.
self.deploy.prestaging(rootfs)
self.apt.apt_update(rootfs)
# Check for packages that are installed.
self.logging.debug("Querying installed packages.")
cache = self.apt.cache(rootfs)
pkgs = self.apt.get_installed_packages(cache)
# Just remove the directory since we are done with it
self.deploy.cleanup(rootfs)
return pkgs