3e63d1dd37
Apply diff anaconda-20.25.16-1..anaconda-21.48.21-1
235 lines
10 KiB
Python
235 lines
10 KiB
Python
# ostreepayload.py
|
|
# Deploy OSTree trees to target
|
|
#
|
|
# Copyright (C) 2012,2014 Red Hat, Inc.
|
|
#
|
|
# This copyrighted material is made available to anyone wishing to use,
|
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
|
# the GNU General Public License v.2, or (at your option) any later version.
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY expressed or implied, including the implied warranties of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
# Public License for more details. You should have received a copy of the
|
|
# GNU General Public License along with this program; if not, write to the
|
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
|
# source code or documentation are not subject to the GNU General Public
|
|
# License and may only be used or replicated with the express permission of
|
|
# Red Hat, Inc.
|
|
#
|
|
# Red Hat Author(s): Colin Walters <walters@redhat.com>
|
|
#
|
|
|
|
import os
|
|
import shutil
|
|
|
|
from pyanaconda import iutil
|
|
from pyanaconda.i18n import _
|
|
from pyanaconda.progress import progressQ
|
|
from gi.repository import GLib
|
|
from gi.repository import Gio
|
|
|
|
from blivet.size import Size
|
|
|
|
import logging
|
|
log = logging.getLogger("anaconda")
|
|
|
|
from pyanaconda.packaging import ArchivePayload, PayloadInstallError
|
|
import pyanaconda.errors as errors
|
|
|
|
class RPMOSTreePayload(ArchivePayload):
|
|
""" A RPMOSTreePayload deploys a tree (possibly with layered packages) onto the target system. """
|
|
def __init__(self, data):
|
|
super(RPMOSTreePayload, self).__init__(data)
|
|
|
|
@property
|
|
def handlesBootloaderConfiguration(self):
|
|
return True
|
|
|
|
@property
|
|
def kernelVersionList(self):
|
|
# OSTree handles bootloader configuration
|
|
return []
|
|
|
|
@property
|
|
def spaceRequired(self):
|
|
# We don't have this data with OSTree at the moment
|
|
return Size("500 MB")
|
|
|
|
def _safeExecWithRedirect(self, cmd, argv, **kwargs):
|
|
"""Like iutil.execWithRedirect, but treat errors as fatal"""
|
|
rc = iutil.execWithRedirect(cmd, argv, **kwargs)
|
|
if rc != 0:
|
|
exn = PayloadInstallError("%s %s exited with code %d" % (cmd, argv, rc))
|
|
if errors.errorHandler.cb(exn) == errors.ERROR_RAISE:
|
|
raise exn
|
|
|
|
def _pullProgressCb(self, asyncProgress):
|
|
status = asyncProgress.get_status()
|
|
outstanding_fetches = asyncProgress.get_uint('outstanding-fetches')
|
|
if status:
|
|
progressQ.send_message(status)
|
|
elif outstanding_fetches > 0:
|
|
bytes_transferred = asyncProgress.get_uint64('bytes-transferred')
|
|
fetched = asyncProgress.get_uint('fetched')
|
|
requested = asyncProgress.get_uint('requested')
|
|
formatted_bytes = GLib.format_size_full(bytes_transferred, 0)
|
|
|
|
if requested == 0:
|
|
percent = 0.0
|
|
else:
|
|
percent = (fetched*1.0 / requested) * 100
|
|
|
|
progressQ.send_message("Receiving objects: %d%% (%d/%d) %s" % (percent, fetched, requested, formatted_bytes))
|
|
else:
|
|
progressQ.send_message("Writing objects")
|
|
|
|
def install(self):
|
|
cancellable = None
|
|
from gi.repository import OSTree
|
|
ostreesetup = self.data.ostreesetup
|
|
log.info("executing ostreesetup=%r", ostreesetup)
|
|
|
|
# Initialize the filesystem - this will create the repo as well
|
|
self._safeExecWithRedirect("ostree",
|
|
["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(),
|
|
"init-fs", iutil.getTargetPhysicalRoot()])
|
|
|
|
repo_arg = "--repo=" + iutil.getTargetPhysicalRoot() + '/ostree/repo'
|
|
|
|
# Set up the chosen remote
|
|
remote_args = [repo_arg, "remote", "add"]
|
|
if ((hasattr(ostreesetup, 'noGpg') and ostreesetup.noGpg) or
|
|
(hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg)):
|
|
remote_args.append("--set=gpg-verify=false")
|
|
remote_args.extend([ostreesetup.remote,
|
|
ostreesetup.url])
|
|
self._safeExecWithRedirect("ostree", remote_args)
|
|
|
|
sysroot_path = Gio.File.new_for_path(iutil.getTargetPhysicalRoot())
|
|
sysroot = OSTree.Sysroot.new(sysroot_path)
|
|
sysroot.load(cancellable)
|
|
|
|
repo = sysroot.get_repo(None)[1]
|
|
repo.set_disable_fsync(True)
|
|
progressQ.send_message(_("Starting pull of %(branchName)s from %(source)s") % \
|
|
{"branchName": ostreesetup.ref, "source": ostreesetup.remote})
|
|
|
|
progress = OSTree.AsyncProgress.new()
|
|
progress.connect('changed', self._pullProgressCb)
|
|
repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable)
|
|
|
|
progressQ.send_message(_("Preparing deployment of %s") % (ostreesetup.ref, ))
|
|
|
|
self._safeExecWithRedirect("ostree",
|
|
["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(),
|
|
"os-init", ostreesetup.osname])
|
|
|
|
admin_deploy_args = ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(),
|
|
"deploy", "--os=" + ostreesetup.osname]
|
|
|
|
admin_deploy_args.append(ostreesetup.remote + ':' + ostreesetup.ref)
|
|
|
|
log.info("ostree admin deploy starting")
|
|
progressQ.send_message(_("Deployment starting: %s") % (ostreesetup.ref, ))
|
|
self._safeExecWithRedirect("ostree", admin_deploy_args)
|
|
log.info("ostree admin deploy complete")
|
|
progressQ.send_message(_("Deployment complete: %s") % (ostreesetup.ref, ))
|
|
|
|
# Reload now that we've deployed, find the path to the new deployment
|
|
sysroot.load(None)
|
|
deployments = sysroot.get_deployments()
|
|
assert len(deployments) > 0
|
|
deployment = deployments[0]
|
|
deployment_path = sysroot.get_deployment_directory(deployment)
|
|
iutil.setSysroot(deployment_path.get_path())
|
|
|
|
# Copy specific bootloader data files from the deployment
|
|
# checkout to the target root. See
|
|
# https://bugzilla.gnome.org/show_bug.cgi?id=726757 This
|
|
# happens once, at installation time.
|
|
# extlinux ships its modules directly in the RPM in /boot.
|
|
# For GRUB2, Anaconda installs device.map there. We may need
|
|
# to add other bootloaders here though (if they can't easily
|
|
# be fixed to *copy* data into /boot at install time, instead
|
|
# of shipping it in the RPM).
|
|
physboot = iutil.getTargetPhysicalRoot() + '/boot'
|
|
sysboot = iutil.getSysroot() + '/boot'
|
|
for fname in ['extlinux', 'grub2']:
|
|
srcpath = os.path.join(sysboot, fname)
|
|
if os.path.isdir(srcpath):
|
|
log.info("Copying bootloader data: " + fname)
|
|
shutil.copytree(srcpath, os.path.join(physboot, fname))
|
|
|
|
def prepareMountTargets(self, storage):
|
|
ostreesetup = self.data.ostreesetup
|
|
|
|
varroot = iutil.getTargetPhysicalRoot() + '/ostree/deploy/' + ostreesetup.osname + '/var'
|
|
|
|
# Set up bind mounts as if we've booted the target system, so
|
|
# that %post script work inside the target.
|
|
binds = [(iutil.getTargetPhysicalRoot(),
|
|
iutil.getSysroot() + '/sysroot'),
|
|
(varroot,
|
|
iutil.getSysroot() + '/var'),
|
|
(iutil.getSysroot() + '/usr', None)]
|
|
|
|
for (src, dest) in binds:
|
|
self._safeExecWithRedirect("mount",
|
|
["--bind", src, dest if dest else src])
|
|
if dest is None:
|
|
self._safeExecWithRedirect("mount",
|
|
["--bind", "-o", "ro", src, src])
|
|
|
|
# Now, ensure that all other potential mount point directories such as
|
|
# (/home) are created. We run through the full tmpfiles here in order
|
|
# to also allow Anaconda and %post scripts to write to directories like
|
|
# /root. We don't iterate *all* tmpfiles because we don't have the
|
|
# matching NSS configuration inside Anaconda, and we can't "chroot" to
|
|
# get it because that would require mounting the API filesystems in the
|
|
# target.
|
|
for varsubdir in ('home', 'roothome', 'lib/rpm', 'opt', 'srv',
|
|
'usrlocal', 'mnt', 'media', 'spool/mail'):
|
|
self._safeExecWithRedirect("systemd-tmpfiles",
|
|
["--create", "--boot", "--root=" + iutil.getSysroot(),
|
|
"--prefix=/var/" + varsubdir])
|
|
|
|
def recreateInitrds(self, force=False):
|
|
# For rpmostree payloads, we're replicating an initramfs from
|
|
# a compose server, and should never be regenerating them
|
|
# per-machine.
|
|
pass
|
|
|
|
def postInstall(self):
|
|
super(RPMOSTreePayload, self).postInstall()
|
|
|
|
physboot = iutil.getTargetPhysicalRoot() + '/boot'
|
|
|
|
# If we're using extlinux, rename extlinux.conf to
|
|
# syslinux.cfg, since that's what OSTree knows about.
|
|
# syslinux upstream supports both, but I'd say that upstream
|
|
# using syslinux.cfg is somewhat preferred.
|
|
physboot_extlinux = physboot + '/extlinux'
|
|
if os.path.isdir(physboot_extlinux):
|
|
physboot_syslinux = physboot + '/syslinux'
|
|
physboot_loader = physboot + '/loader'
|
|
assert os.path.isdir(physboot_loader)
|
|
orig_extlinux_conf = physboot_extlinux + '/extlinux.conf'
|
|
target_syslinux_cfg = physboot_loader + '/syslinux.cfg'
|
|
log.info("Moving %s -> %s", orig_extlinux_conf, target_syslinux_cfg)
|
|
os.rename(orig_extlinux_conf, target_syslinux_cfg)
|
|
# A compatibility bit for OSTree
|
|
os.mkdir(physboot_syslinux)
|
|
os.symlink('../loader/syslinux.cfg', physboot_syslinux + '/syslinux.cfg')
|
|
# And *also* tell syslinux that the config is really in /boot/loader
|
|
os.symlink('loader/syslinux.cfg', physboot + '/syslinux.cfg')
|
|
|
|
# OSTree owns the bootloader configuration, so here we give it
|
|
# the argument list we computed from storage, architecture and
|
|
# such.
|
|
set_kargs_args = ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(),
|
|
"instutil", "set-kargs"]
|
|
set_kargs_args.extend(self.storage.bootloader.boot_args)
|
|
set_kargs_args.append("root=" + self.storage.rootDevice.fstabSpec)
|
|
self._safeExecWithRedirect("ostree", set_kargs_args)
|