Write original as recipe.toml and the depsolved version as frozen.toml Also write 'WAITING' to the STATUS file as its first state. The STATUS states are now WAITING -> RUNNING -> FINISHED|FAILED
208 lines
7.5 KiB
Python
208 lines
7.5 KiB
Python
# Copyright (C) 2018 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/>.
|
|
#
|
|
""" Setup for composing an image
|
|
|
|
Adding New Output Types
|
|
-----------------------
|
|
|
|
The new output type must add a kickstart template to ./share/composer/ where the
|
|
name of the kickstart (without the trailing .ks) matches the entry in compose_args.
|
|
|
|
The kickstart should not have any url or repo entries, these will be added at build
|
|
time. The %packages section should be the last thing, and while it can contain mandatory
|
|
packages required by the output type, it should not have the trailing %end because the
|
|
package NEVRAs will be appended to it at build time.
|
|
|
|
compose_args should have a name matching the kickstart, and it should set the novirt_install
|
|
parameters needed to generate the desired output. Other types should be set to False.
|
|
|
|
"""
|
|
import logging
|
|
log = logging.getLogger("lorax-composer")
|
|
|
|
import os
|
|
from glob import glob
|
|
import pytoml as toml
|
|
import shutil
|
|
from uuid import uuid4
|
|
|
|
from pylorax.api.projects import projects_depsolve, dep_nevra
|
|
from pylorax.api.projects import ProjectsError
|
|
from pylorax.imgutils import default_image_name
|
|
from pylorax.sysutils import joinpaths
|
|
|
|
|
|
def repo_to_ks(r, url="url"):
|
|
""" Return a kickstart line with the correct args.
|
|
|
|
Set url to "baseurl" if it is a repo, leave it as "url" for the installation url.
|
|
"""
|
|
cmd = ""
|
|
if r.metalink:
|
|
# XXX Total Hack
|
|
# RHEL7 kickstart doesn't support metalink. If the url has 'metalink' in it, rewrite it as 'mirrorlist'
|
|
if "metalink" in r.metalink:
|
|
log.info("RHEL7 does not support metalink, translating to mirrorlist")
|
|
cmd += '--mirrorlist="%s" ' % r.metalink.replace("metalink", "mirrorlist")
|
|
else:
|
|
log.error("Could not convert metalink to mirrorlist. %s", r.metalink)
|
|
raise RuntimeError("Cannot convert metalink to mirrorlist: %s" % r.metalink)
|
|
elif r.mirrorlist:
|
|
cmd += '--mirrorlist="%s" ' % r.mirrorlist
|
|
elif r.baseurl:
|
|
cmd += '--%s="%s" ' % (url, r.baseurl[0])
|
|
else:
|
|
raise RuntimeError("Repo has no baseurl or mirror")
|
|
|
|
if r.proxy:
|
|
cmd += '--proxy="%s" ' % r.proxy
|
|
|
|
if not r.sslverify:
|
|
cmd += '--noverifyssl'
|
|
|
|
return cmd
|
|
|
|
def start_build(cfg, yumlock, recipe, compose_type):
|
|
""" Start the build
|
|
|
|
:param cfg: Configuration object
|
|
:type cfg: ComposerConfig
|
|
:param yumlock: Lock and YumBase for depsolving
|
|
:type yumlock: YumLock
|
|
:param recipe: The recipe to build
|
|
:type recipe: str
|
|
:param compose_type: The type of output to create from the recipe
|
|
:type compose_type: str
|
|
:returns: Unique ID for the build that can be used to track its status
|
|
:rtype: str
|
|
"""
|
|
share_dir = cfg.get("composer", "share_dir")
|
|
lib_dir = cfg.get("composer", "lib_dir")
|
|
|
|
# Make sure compose_type is valid
|
|
if compose_type not in compose_types(share_dir):
|
|
raise RuntimeError("Invalid compose type (%s), must be one of %s" % (compose_type, compose_types(share_dir)))
|
|
|
|
# Combine modules and packages and depsolve the list
|
|
# TODO include the version/glob in the depsolving
|
|
module_names = map(lambda m: m["name"], recipe["modules"] or [])
|
|
package_names = map(lambda p: p["name"], recipe["packages"] or [])
|
|
projects = sorted(set(module_names+package_names), key=lambda n: n.lower())
|
|
deps = []
|
|
try:
|
|
with yumlock.lock:
|
|
deps = projects_depsolve(yumlock.yb, projects)
|
|
except ProjectsError as e:
|
|
log.error("start_build depsolve: %s", str(e))
|
|
raise RuntimeError("Problem depsolving %s: %s" % (recipe["name"], str(e)))
|
|
|
|
# Create the results directory
|
|
build_id = str(uuid4())
|
|
results_dir = joinpaths(lib_dir, "results", build_id)
|
|
os.makedirs(results_dir)
|
|
|
|
# Write the original recipe
|
|
recipe_path = joinpaths(results_dir, "recipe.toml")
|
|
with open(recipe_path, "w") as f:
|
|
f.write(recipe.toml())
|
|
|
|
# Write the frozen recipe
|
|
frozen_recipe = recipe.freeze(deps)
|
|
recipe_path = joinpaths(results_dir, "frozen.toml")
|
|
with open(recipe_path, "w") as f:
|
|
f.write(frozen_recipe.toml())
|
|
|
|
# Read the kickstart template for this type and copy it into the results
|
|
ks_template_path = joinpaths(share_dir, "composer", compose_type) + ".ks"
|
|
shutil.copy(ks_template_path, results_dir)
|
|
ks_template = open(ks_template_path, "r").read()
|
|
|
|
# Write out the dependencies to the results dir
|
|
deps_path = joinpaths(results_dir, "deps.txt")
|
|
with open(deps_path, "w") as f:
|
|
for d in deps:
|
|
f.write(dep_nevra(d)+"\n")
|
|
|
|
# Create the final kickstart with repos and package list
|
|
ks_path = joinpaths(results_dir, "final-kickstart.ks")
|
|
with open(ks_path, "w") as f:
|
|
with yumlock.lock:
|
|
repos = yumlock.yb.repos.listEnabled()
|
|
if not repos:
|
|
raise RuntimeError("No enabled repos, canceling build.")
|
|
|
|
ks_url = repo_to_ks(repos[0], "url")
|
|
log.debug("url = %s", ks_url)
|
|
f.write('url %s\n' % ks_url)
|
|
for idx, r in enumerate(repos[1:]):
|
|
ks_repo = repo_to_ks(r, "baseurl")
|
|
log.debug("repo composer-%s = %s", idx, ks_repo)
|
|
f.write('repo --name="composer-%s" %s\n' % (idx, ks_repo))
|
|
|
|
f.write(ks_template)
|
|
|
|
for d in deps:
|
|
f.write(dep_nevra(d)+"\n")
|
|
|
|
f.write("%end\n")
|
|
|
|
# Setup the config to pass to novirt_install
|
|
log_dir = joinpaths(results_dir, "logs/")
|
|
cfg_args = compose_args(compose_type)
|
|
cfg_args.update({
|
|
"compression": "xz",
|
|
#"compress_args": ["-9"],
|
|
"compress_args": [],
|
|
"ks": [ks_path],
|
|
"anaconda_args": "",
|
|
"proxy": "",
|
|
"armplatform": "",
|
|
|
|
"project": "Red Hat Enterprise Linux",
|
|
"releasever": "7",
|
|
|
|
"logfile": log_dir
|
|
})
|
|
with open(joinpaths(results_dir, "config.toml"), "w") as f:
|
|
f.write(toml.dumps(cfg_args).encode("UTF-8"))
|
|
|
|
# Set the initial status
|
|
open(joinpaths(results_dir, "STATUS"), "w").write("WAITING")
|
|
|
|
log.info("Adding %s with recipe %s output type %s to compose queue", build_id, recipe["name"], compose_type)
|
|
os.symlink(results_dir, joinpaths(lib_dir, "queue/new/", build_id))
|
|
|
|
return build_id
|
|
|
|
# Supported output types
|
|
def compose_types(share_dir):
|
|
""" Returns a list of the supported output types
|
|
|
|
The output types come from the kickstart names in /usr/share/lorax/composer/*ks
|
|
"""
|
|
return [os.path.basename(ks)[:-3] for ks in glob(joinpaths(share_dir, "composer/*.ks"))]
|
|
|
|
def compose_args(compose_type):
|
|
""" Returns the settings to pass to novirt_install for the compose type"""
|
|
_MAP = {"tar": {"make_tar": True,
|
|
"make_iso": False,
|
|
"make_fsimage": False,
|
|
"qcow2": False,
|
|
"image_name": default_image_name("xz", "root.tar")},
|
|
}
|
|
|
|
return _MAP[compose_type]
|