All these are calling subprocess in 'text mode', where it will try to decode stdout/stderr using the default encoding (utf-8 for us). If it doesn't decode, subprocess will raise an exception and kobo doesn't handle it, it just passes it along to us, so things blow up - see https://pagure.io/releng/issue/12474 . To avoid this, let's set `errors="replace"`, which tells the decoder to replace invalid data with ? characters. This way we should get as much of the output as can be read, and no crashes. We also replace `universal_newlines=True` with `text=True` as the latter is shorter, clearer, and what Python 3 subprocess wants us to use, it considers `universal_newlines` to just be a backwards-compatibility thing - "The universal_newlines argument is equivalent to text and is provided for backwards compatibility" Signed-off-by: Adam Williamson <awilliam@redhat.com> Merges: https://pagure.io/pungi/pull-request/1812
188 lines
6.9 KiB
Python
188 lines
6.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import copy
|
|
import json
|
|
import os
|
|
from kobo import shortcuts
|
|
from kobo.threads import ThreadPool, WorkerThread
|
|
|
|
from productmd.images import Image
|
|
|
|
from pungi.runroot import Runroot
|
|
from .base import ConfigGuardedPhase
|
|
from .. import util
|
|
from ..util import get_repo_dicts, translate_path
|
|
from ..wrappers import scm
|
|
|
|
|
|
class OSTreeContainerPhase(ConfigGuardedPhase):
|
|
name = "ostree_container"
|
|
|
|
def __init__(self, compose, pkgset_phase=None):
|
|
super(OSTreeContainerPhase, self).__init__(compose)
|
|
self.pool = ThreadPool(logger=self.compose._logger)
|
|
self.pkgset_phase = pkgset_phase
|
|
|
|
def get_repos(self):
|
|
return [
|
|
translate_path(
|
|
self.compose,
|
|
self.compose.paths.work.pkgset_repo(
|
|
pkgset.name, "$basearch", create_dir=False
|
|
),
|
|
)
|
|
for pkgset in self.pkgset_phase.package_sets
|
|
]
|
|
|
|
def _enqueue(self, variant, arch, conf):
|
|
self.pool.add(OSTreeContainerThread(self.pool, self.get_repos()))
|
|
self.pool.queue_put((self.compose, variant, arch, conf))
|
|
|
|
def run(self):
|
|
if isinstance(self.compose.conf.get(self.name), dict):
|
|
for variant in self.compose.get_variants():
|
|
for conf in self.get_config_block(variant):
|
|
for arch in conf.get("arches", []) or variant.arches:
|
|
self._enqueue(variant, arch, conf)
|
|
else:
|
|
# Legacy code path to support original configuration.
|
|
for variant in self.compose.get_variants():
|
|
for arch in variant.arches:
|
|
for conf in self.get_config_block(variant, arch):
|
|
self._enqueue(variant, arch, conf)
|
|
|
|
self.pool.start()
|
|
|
|
|
|
class OSTreeContainerThread(WorkerThread):
|
|
def __init__(self, pool, repos):
|
|
super(OSTreeContainerThread, self).__init__(pool)
|
|
self.repos = repos
|
|
|
|
def process(self, item, num):
|
|
compose, variant, arch, config = item
|
|
self.num = num
|
|
failable_arches = config.get("failable", [])
|
|
self.can_fail = util.can_arch_fail(failable_arches, arch)
|
|
with util.failable(compose, self.can_fail, variant, arch, "ostree-container"):
|
|
self.worker(compose, variant, arch, config)
|
|
|
|
def worker(self, compose, variant, arch, config):
|
|
msg = "OSTree container phase for variant %s, arch %s" % (variant.uid, arch)
|
|
self.pool.log_info("[BEGIN] %s" % msg)
|
|
workdir = compose.paths.work.topdir("ostree-container-%d" % self.num)
|
|
self.logdir = compose.paths.log.topdir(
|
|
"%s/%s/ostree-container-%d" % (arch, variant.uid, self.num)
|
|
)
|
|
repodir = os.path.join(workdir, "config_repo")
|
|
self._clone_repo(
|
|
compose,
|
|
repodir,
|
|
config["config_url"],
|
|
config.get("config_branch", "main"),
|
|
)
|
|
|
|
repos = shortcuts.force_list(config.get("repo", [])) + self.repos
|
|
repos = get_repo_dicts(repos, logger=self.pool)
|
|
|
|
# copy the original config and update before save to a json file
|
|
new_config = copy.copy(config)
|
|
|
|
# repos in configuration can have repo url set to variant UID,
|
|
# update it to have the actual url that we just translated.
|
|
new_config.update({"repo": repos})
|
|
|
|
# remove unnecessary (for 'pungi-make-ostree container' script ) elements
|
|
# from config, it doesn't hurt to have them, however remove them can
|
|
# reduce confusion
|
|
for k in [
|
|
"treefile",
|
|
"config_url",
|
|
"config_branch",
|
|
"failable",
|
|
"version",
|
|
]:
|
|
new_config.pop(k, None)
|
|
|
|
# write a json file to save the configuration, so 'pungi-make-ostree tree'
|
|
# can take use of it
|
|
extra_config_file = os.path.join(workdir, "extra_config.json")
|
|
with open(extra_config_file, "w") as f:
|
|
json.dump(new_config, f, indent=4)
|
|
|
|
self._run_ostree_container_cmd(
|
|
compose, variant, arch, config, repodir, extra_config_file=extra_config_file
|
|
)
|
|
|
|
self.pool.log_info("[DONE ] %s" % (msg))
|
|
|
|
def _run_ostree_container_cmd(
|
|
self, compose, variant, arch, config, config_repo, extra_config_file=None
|
|
):
|
|
subvariant = config.get("subvariant", variant.uid)
|
|
target_dir = compose.paths.compose.image_dir(variant) % {"arch": arch}
|
|
util.makedirs(target_dir)
|
|
version = util.version_generator(compose, config.get("version"))
|
|
anb = config.get("name", "%s-%s" % (compose.conf["release_short"], subvariant))
|
|
archive_name = "%s-%s-%s" % (anb, arch, version)
|
|
|
|
# Run the pungi-make-ostree command locally to create a script to
|
|
# execute in runroot environment.
|
|
cmd = [
|
|
"pungi-make-ostree",
|
|
"container",
|
|
"--log-dir=%s" % self.logdir,
|
|
"--name=%s" % archive_name,
|
|
"--path=%s" % target_dir,
|
|
"--treefile=%s" % os.path.join(config_repo, config["treefile"]),
|
|
"--extra-config=%s" % extra_config_file,
|
|
"--version=%s" % version,
|
|
]
|
|
|
|
_, runroot_script = shortcuts.run(cmd, text=True, errors="replace")
|
|
|
|
default_packages = ["ostree", "rpm-ostree", "selinux-policy-targeted"]
|
|
additional_packages = config.get("runroot_packages", [])
|
|
packages = default_packages + additional_packages
|
|
log_file = os.path.join(self.logdir, "runroot.log")
|
|
# TODO: Use to get previous build
|
|
mounts = [compose.topdir]
|
|
|
|
runroot = Runroot(compose, phase="ostree_container")
|
|
runroot.run(
|
|
" && ".join(runroot_script.splitlines()),
|
|
log_file=log_file,
|
|
arch=arch,
|
|
packages=packages,
|
|
mounts=mounts,
|
|
new_chroot=True,
|
|
weight=compose.conf["runroot_weights"].get("ostree"),
|
|
)
|
|
|
|
fullpath = os.path.join(target_dir, "%s.ociarchive" % archive_name)
|
|
|
|
# Update image manifest
|
|
img = Image(compose.im)
|
|
|
|
# these are hardcoded as they should always be correct, we
|
|
# could potentially allow overriding them via config though
|
|
img.type = "bootable-container"
|
|
img.format = "ociarchive"
|
|
img.path = os.path.relpath(fullpath, compose.paths.compose.topdir())
|
|
img.mtime = util.get_mtime(fullpath)
|
|
img.size = util.get_file_size(fullpath)
|
|
img.arch = arch
|
|
img.disc_number = 1
|
|
img.disc_count = 1
|
|
img.bootable = False
|
|
img.subvariant = subvariant
|
|
setattr(img, "can_fail", self.can_fail)
|
|
setattr(img, "deliverable", "ostree-container")
|
|
compose.im.add(variant=variant.uid, arch=arch, image=img)
|
|
|
|
def _clone_repo(self, compose, repodir, url, branch):
|
|
scm.get_dir_from_scm(
|
|
{"scm": "git", "repo": url, "branch": branch, "dir": "."},
|
|
repodir,
|
|
compose=compose,
|
|
)
|