Make runtime directly into squashfs image. This reduces largely unreproducible ext4 layer, but requires anaconda's dracut module modification to properly mount the image.
316 lines
11 KiB
Python
Executable File
316 lines
11 KiB
Python
Executable File
#!/usr/bin/python3
|
|
#
|
|
# lorax
|
|
#
|
|
# Copyright (C) 2009-2015 Red Hat, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# Red Hat Author(s): Martin Gracik <mgracik@redhat.com>
|
|
#
|
|
import logging
|
|
log = logging.getLogger("lorax")
|
|
dnf_log = logging.getLogger("dnf")
|
|
|
|
|
|
import sys
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
|
|
import dnf
|
|
import dnf.logging
|
|
import librepo
|
|
import pylorax
|
|
from pylorax import DRACUT_DEFAULT, DEFAULT_PLATFORM_ID
|
|
from pylorax.cmdline import lorax_parser
|
|
from pylorax.sysutils import flatconfig
|
|
import selinux
|
|
|
|
def setup_logging(opts):
|
|
pylorax.setup_logging(opts.logfile, log)
|
|
|
|
# dnf logging
|
|
dnf_log.setLevel(dnf.logging.DDEBUG)
|
|
logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/dnf.log"
|
|
fh = logging.FileHandler(filename=logfile, mode="w")
|
|
fh.setLevel(logging.NOTSET)
|
|
dnf_log.addHandler(fh)
|
|
|
|
# Setup librepo logging
|
|
logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/dnf.librepo.log"
|
|
librepo.log_set_file(logfile)
|
|
|
|
|
|
def main():
|
|
parser = lorax_parser(DRACUT_DEFAULT)
|
|
opts = parser.parse_args()
|
|
|
|
log.info("Lorax v%s", pylorax.vernum)
|
|
|
|
if not opts.source and not opts.repos:
|
|
parser.error("--source, --repo, or both are required.")
|
|
|
|
if not opts.force and os.path.exists(opts.outputdir):
|
|
parser.error("output directory %s should not exist." % opts.outputdir)
|
|
|
|
if not os.path.exists(os.path.dirname(opts.logfile)):
|
|
os.makedirs(os.path.dirname(opts.logfile))
|
|
if opts.sharedir and not os.path.isdir(opts.sharedir):
|
|
parser.error("sharedir %s doesn't exist." % opts.sharedir)
|
|
if opts.config and not os.path.exists(opts.config):
|
|
parser.error("config file %s doesn't exist." % opts.config)
|
|
|
|
setup_logging(opts)
|
|
|
|
log.info("checking the selinux mode")
|
|
if selinux.is_selinux_enabled() and selinux.security_getenforce():
|
|
log.critical("selinux must be disabled or in Permissive mode")
|
|
sys.exit(1)
|
|
|
|
if not opts.workdir:
|
|
tempfile.tempdir = opts.tmp
|
|
|
|
# create the temporary directory for lorax
|
|
tempdir = tempfile.mkdtemp(prefix="lorax.", dir=tempfile.gettempdir())
|
|
else:
|
|
tempdir = opts.workdir
|
|
if not os.path.exists(tempdir):
|
|
os.makedirs(tempdir)
|
|
|
|
installtree = os.path.join(tempdir, "installtree")
|
|
if not os.path.exists(installtree):
|
|
os.mkdir(installtree)
|
|
dnftempdir = os.path.join(tempdir, "dnf")
|
|
if not os.path.exists(dnftempdir):
|
|
os.mkdir(dnftempdir)
|
|
|
|
dnfbase = get_dnf_base_object(installtree, opts.source, opts.mirrorlist, opts.repos,
|
|
opts.enablerepos, opts.disablerepos,
|
|
dnftempdir, opts.proxy, opts.version, opts.cachedir,
|
|
os.path.dirname(opts.logfile), not opts.noverifyssl,
|
|
opts.dnfplugins)
|
|
|
|
if dnfbase is None:
|
|
print("error: unable to create the dnf base object", file=sys.stderr)
|
|
if not opts.workdir:
|
|
shutil.rmtree(tempdir)
|
|
sys.exit(1)
|
|
|
|
parsed_add_template_vars = {}
|
|
for kv in opts.add_template_vars:
|
|
k, t, v = kv.partition('=')
|
|
if t == '':
|
|
raise ValueError("Missing '=' for key=value in " % kv)
|
|
parsed_add_template_vars[k] = v
|
|
|
|
parsed_add_arch_template_vars = {}
|
|
for kv in opts.add_arch_template_vars:
|
|
k, t, v = kv.partition('=')
|
|
if t == '':
|
|
raise ValueError("Missing '=' for key=value in " % kv)
|
|
parsed_add_arch_template_vars[k] = v
|
|
|
|
if 'SOURCE_DATE_EPOCH' in os.environ:
|
|
log.info("Using SOURCE_DATE_EPOCH=%s as the current time.", os.environ["SOURCE_DATE_EPOCH"])
|
|
|
|
# run lorax
|
|
lorax = pylorax.Lorax()
|
|
lorax.configure(conf_file=opts.config)
|
|
lorax.conf.set("lorax", "logdir", os.path.dirname(opts.logfile))
|
|
|
|
# Override the config file's template sharedir
|
|
if opts.sharedir:
|
|
lorax.conf.set("lorax", "sharedir", opts.sharedir)
|
|
|
|
lorax.run(dnfbase, opts.product, opts.version, opts.release,
|
|
opts.variant, opts.bugurl, opts.isfinal,
|
|
workdir=tempdir, outputdir=opts.outputdir, buildarch=opts.buildarch,
|
|
volid=opts.volid, domacboot=opts.domacboot, doupgrade=opts.doupgrade,
|
|
installpkgs=opts.installpkgs, excludepkgs=opts.excludepkgs,
|
|
size=opts.rootfs_size,
|
|
add_templates=opts.add_templates,
|
|
add_template_vars=parsed_add_template_vars,
|
|
add_arch_templates=opts.add_arch_templates,
|
|
add_arch_template_vars=parsed_add_arch_template_vars,
|
|
remove_temp=True, verify=opts.verify,
|
|
user_dracut_args=opts.dracut_args,
|
|
squashfs_only=opts.squashfs_only)
|
|
|
|
|
|
def get_dnf_base_object(installroot, sources, mirrorlists=None, repos=None,
|
|
enablerepos=None, disablerepos=None,
|
|
tempdir="/var/tmp", proxy=None, releasever="29",
|
|
cachedir=None, logdir=None, sslverify=True, dnfplugins=None):
|
|
""" Create a dnf Base object and setup the repositories and installroot
|
|
|
|
:param string installroot: Full path to the installroot
|
|
:param list sources: List of source repo urls to use for the installation
|
|
:param list enablerepos: List of repo names to enable
|
|
:param list disablerepos: List of repo names to disable
|
|
:param list mirrorlist: List of mirrors to use
|
|
:param string tempdir: Path of temporary directory
|
|
:param string proxy: http proxy to use when fetching packages
|
|
:param string releasever: Release version to pass to dnf
|
|
:param string cachedir: Directory to use for caching packages
|
|
:param bool noverifyssl: Set to True to ignore the CA of ssl certs. eg. use self-signed ssl for https repos.
|
|
|
|
If tempdir is not set /var/tmp is used.
|
|
If cachedir is None a dnf.cache directory is created inside tmpdir
|
|
"""
|
|
def sanitize_repo(repo):
|
|
"""Convert bare paths to file:/// URIs, and silently reject protocols unhandled by yum"""
|
|
if repo.startswith("/"):
|
|
return "file://{0}".format(repo)
|
|
elif any(repo.startswith(p) for p in ('http://', 'https://', 'ftp://', 'file://')):
|
|
return repo
|
|
else:
|
|
return None
|
|
|
|
mirrorlists = mirrorlists or []
|
|
|
|
# sanitize the repositories
|
|
sources = list(sanitize_repo(r) for r in sources)
|
|
mirrorlists = list(sanitize_repo(r) for r in mirrorlists)
|
|
|
|
# remove invalid repositories
|
|
sources = list(r for r in sources if r)
|
|
mirrorlists = list(r for r in mirrorlists if r)
|
|
|
|
if not cachedir:
|
|
cachedir = os.path.join(tempdir, "dnf.cache")
|
|
if not os.path.isdir(cachedir):
|
|
os.mkdir(cachedir)
|
|
|
|
if not logdir:
|
|
logdir = os.path.join(tempdir, "dnf.logs")
|
|
if not os.path.isdir(logdir):
|
|
os.mkdir(logdir)
|
|
|
|
dnfbase = dnf.Base()
|
|
# Enable DNF pluings
|
|
# NOTE: These come from the HOST system's environment
|
|
if dnfplugins:
|
|
if dnfplugins[0] == "*":
|
|
# Enable them all
|
|
dnfbase.init_plugins()
|
|
else:
|
|
# Only enable the listed plugins
|
|
dnfbase.init_plugins(disabled_glob=["*"], enable_plugins=dnfplugins)
|
|
conf = dnfbase.conf
|
|
conf.logdir = logdir
|
|
conf.cachedir = cachedir
|
|
|
|
conf.install_weak_deps = False
|
|
conf.releasever = releasever
|
|
conf.installroot = installroot
|
|
conf.prepend_installroot('persistdir')
|
|
# this is a weird 'AppendOption' thing that, when you set it,
|
|
# actually appends. Doing this adds 'nodocs' to the existing list
|
|
# of values, over in libdnf, it does not replace the existing values.
|
|
conf.tsflags = ['nodocs']
|
|
|
|
if proxy:
|
|
conf.proxy = proxy
|
|
|
|
if sslverify == False:
|
|
conf.sslverify = False
|
|
|
|
# DNF 3.2 needs to have module_platform_id set, otherwise depsolve won't work correctly
|
|
if not os.path.exists("/etc/os-release"):
|
|
log.warning("/etc/os-release is missing, cannot determine platform id, falling back to %s", DEFAULT_PLATFORM_ID)
|
|
platform_id = DEFAULT_PLATFORM_ID
|
|
else:
|
|
os_release = flatconfig("/etc/os-release")
|
|
platform_id = os_release.get("PLATFORM_ID", DEFAULT_PLATFORM_ID)
|
|
log.info("Using %s for module_platform_id", platform_id)
|
|
conf.module_platform_id = platform_id
|
|
|
|
# Add .repo files
|
|
if repos:
|
|
reposdir = os.path.join(tempdir, "dnf.repos")
|
|
if not os.path.isdir(reposdir):
|
|
os.mkdir(reposdir)
|
|
for r in repos:
|
|
shutil.copy2(r, reposdir)
|
|
conf.reposdir = [reposdir]
|
|
dnfbase.read_all_repos()
|
|
|
|
# add the sources
|
|
for i, r in enumerate(sources):
|
|
if "SRPM" in r or "srpm" in r:
|
|
log.info("Skipping source repo: %s", r)
|
|
continue
|
|
repo_name = "lorax-repo-%d" % i
|
|
repo = dnf.repo.Repo(repo_name, conf)
|
|
repo.baseurl = [r]
|
|
if proxy:
|
|
repo.proxy = proxy
|
|
repo.enable()
|
|
dnfbase.repos.add(repo)
|
|
log.info("Added '%s': %s", repo_name, r)
|
|
log.info("Fetching metadata...")
|
|
try:
|
|
repo.load()
|
|
except dnf.exceptions.RepoError as e:
|
|
log.error("Error fetching metadata for %s: %s", repo_name, e)
|
|
return None
|
|
|
|
# add the mirrorlists
|
|
for i, r in enumerate(mirrorlists):
|
|
if "SRPM" in r or "srpm" in r:
|
|
log.info("Skipping source repo: %s", r)
|
|
continue
|
|
repo_name = "lorax-mirrorlist-%d" % i
|
|
repo = dnf.repo.Repo(repo_name, conf)
|
|
repo.mirrorlist = r
|
|
if proxy:
|
|
repo.proxy = proxy
|
|
repo.enable()
|
|
dnfbase.repos.add(repo)
|
|
log.info("Added '%s': %s", repo_name, r)
|
|
log.info("Fetching metadata...")
|
|
try:
|
|
repo.load()
|
|
except dnf.exceptions.RepoError as e:
|
|
log.error("Error fetching metadata for %s: %s", repo_name, e)
|
|
return None
|
|
|
|
# Enable repos listed on the cmdline
|
|
for r in enablerepos:
|
|
repolist = dnfbase.repos.get_matching(r)
|
|
if not repolist:
|
|
log.warning("%s is an unknown repo, not enabling it", r)
|
|
else:
|
|
repolist.enable()
|
|
log.info("Enabled repo %s", r)
|
|
|
|
# Disable repos listed on the cmdline
|
|
for r in disablerepos:
|
|
repolist = dnfbase.repos.get_matching(r)
|
|
if not repolist:
|
|
log.warning("%s is an unknown repo, not disabling it", r)
|
|
else:
|
|
repolist.disable()
|
|
log.info("Disabled repo %s", r)
|
|
|
|
dnfbase.fill_sack(load_system_repo=False)
|
|
dnfbase.read_comps()
|
|
|
|
return dnfbase
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|