From ed03ac7524c6c5a9ceb08500521325ffe162682c Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Mon, 5 Feb 2018 14:27:24 -0800 Subject: [PATCH] Add /compose/info route to retrieve details about a compose The results is a JSON string with the following information: * id - The uuid of the comoposition * config - containing the configuration settings used to run Anaconda * recipe - The depsolved recipe used to generate the kickstart * commit - The (local) git commit hash for the recipe used * deps - The NEVRA of all of the dependencies used in the composition * compose_type - The type of output generated (tar, iso, etc.) * queue_status - The final status of the composition (FINISHED or FAILED) --- src/pylorax/api/compose.py | 16 ++++++-- src/pylorax/api/queue.py | 83 +++++++++++++++++++++++++++++++++----- src/pylorax/api/v0.py | 71 +++++++++++++++++++++++++++----- 3 files changed, 147 insertions(+), 23 deletions(-) diff --git a/src/pylorax/api/compose.py b/src/pylorax/api/compose.py index 4e503a0c..d0a58cbd 100644 --- a/src/pylorax/api/compose.py +++ b/src/pylorax/api/compose.py @@ -41,6 +41,7 @@ from uuid import uuid4 from pylorax.api.projects import projects_depsolve, dep_nevra from pylorax.api.projects import ProjectsError +from pylorax.api.recipes import read_recipe_and_id from pylorax.imgutils import default_image_name from pylorax.sysutils import joinpaths @@ -75,7 +76,7 @@ def repo_to_ks(r, url="url"): return cmd -def start_build(cfg, yumlock, recipe, compose_type): +def start_build(cfg, yumlock, gitlock, branch, recipe_name, compose_type): """ Start the build :param cfg: Configuration object @@ -96,6 +97,9 @@ def start_build(cfg, yumlock, recipe, compose_type): 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))) + with gitlock.lock: + (commit_id, recipe) = read_recipe_and_id(gitlock.repo, branch, recipe_name) + # 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 []) @@ -114,6 +118,11 @@ def start_build(cfg, yumlock, recipe, compose_type): results_dir = joinpaths(lib_dir, "results", build_id) os.makedirs(results_dir) + # Write the recipe commit hash + commit_path = joinpaths(results_dir, "COMMIT") + with open(commit_path, "w") as f: + f.write(commit_id) + # Write the original recipe recipe_path = joinpaths(results_dir, "recipe.toml") with open(recipe_path, "w") as f: @@ -131,10 +140,9 @@ def start_build(cfg, yumlock, recipe, compose_type): ks_template = open(ks_template_path, "r").read() # Write out the dependencies to the results dir - deps_path = joinpaths(results_dir, "deps.txt") + deps_path = joinpaths(results_dir, "deps.toml") with open(deps_path, "w") as f: - for d in deps: - f.write(dep_nevra(d)+"\n") + f.write(toml.dumps({"packages":deps}).encode("UTF-8")) # Create the final kickstart with repos and package list ks_path = joinpaths(results_dir, "final-kickstart.ks") diff --git a/src/pylorax/api/queue.py b/src/pylorax/api/queue.py index 5183e03e..4ad4cfae 100644 --- a/src/pylorax/api/queue.py +++ b/src/pylorax/api/queue.py @@ -122,6 +122,17 @@ def make_compose(cfg, results_dir): log.debug("Install finished, chowning results to %s:%s", user, group) subprocess.call(["chown", "-R", "%s:%s" % (user, group), results_dir]) +def get_compose_type(results_dir): + """ Return the type of composition. + + """ + # Should only be 2 kickstarts, the final-kickstart.ks and the template + t = [os.path.basename(ks)[:-3] for ks in glob(joinpaths(results_dir, "*.ks")) + if "final-kickstart" not in ks] + if len(t) != 1: + raise RuntimeError("Cannot find ks template for build %s" % os.path.basename(results_dir)) + return t[0] + def compose_detail(results_dir): """ Return details about the build.""" @@ -134,17 +145,14 @@ def compose_detail(results_dir): mtime = os.stat(joinpaths(results_dir, "STATUS")).st_mtime recipe = recipe_from_file(joinpaths(results_dir, "recipe.toml")) - # Should only be 2 kickstarts, the final-kickstart.ks and the template - types = [os.path.basename(ks)[:-3] for ks in glob(joinpaths(results_dir, "*.ks")) - if "final-kickstart" not in ks] - if len(types) != 1: - raise RuntimeError("Cannot find ks template for build %s" % build_id) + compose_type = get_compose_type(results_dir) - return {"id": build_id, - "status": status, - "timestamp":mtime, - "recipe": recipe["name"], - "version": recipe["version"] + return {"id": build_id, + "queue_status": status, + "timestamp": mtime, + "compose_type": compose_type, + "recipe": recipe["name"], + "version": recipe["version"] } def queue_status(cfg): @@ -218,3 +226,58 @@ def uuid_delete(cfg, uuid): raise RuntimeError("Directory length is too short: %s" % uuid_dir) shutil.rmtree(uuid_dir) return True + +def uuid_info(cfg, uuid): + """Return information about the composition + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param uuid: The UUID of the build + :type uuid: str + :returns: dictionary of information about the composition + :rtype: dict + + This will return a dict with the following fields populated: + + * id - The uuid of the comoposition + * config - containing the configuration settings used to run Anaconda + * recipe - The depsolved recipe used to generate the kickstart + * commit - The (local) git commit hash for the recipe used + * deps - The NEVRA of all of the dependencies used in the composition + * compose_type - The type of output generated (tar, iso, etc.) + * queue_status - The final status of the composition (FINISHED or FAILED) + """ + uuid_dir = joinpaths(cfg.get("composer", "lib_dir"), "results", uuid) + if not os.path.exists(uuid_dir): + raise RuntimeError("%s is not a valid build_id" % uuid) + + # Load the compose configuration + cfg_path = joinpaths(uuid_dir, "config.toml") + if not os.path.exists(cfg_path): + raise RuntimeError("Missing config.toml for %s" % uuid) + cfg_dict = toml.loads(open(cfg_path, "r").read()) + + frozen_path = joinpaths(uuid_dir, "frozen.toml") + if not os.path.exists(frozen_path): + raise RuntimeError("Missing frozen.toml for %s" % uuid) + frozen_dict = toml.loads(open(frozen_path, "r").read()) + + deps_path = joinpaths(uuid_dir, "deps.toml") + if not os.path.exists(deps_path): + raise RuntimeError("Missing deps.toml for %s" % uuid) + deps_dict = toml.loads(open(deps_path, "r").read()) + + compose_type = get_compose_type(uuid_dir) + status = open(joinpaths(uuid_dir, "STATUS")).read().strip() + + commit_path = joinpaths(uuid_dir, "COMMIT") + commit_id = open(commit_path, "r").read().strip() + + return {"id": uuid, + "config": cfg_dict, + "recipe": frozen_dict, + "commit": commit_id, + "deps": deps_dict, + "compose_type": compose_type, + "queue_status": status + } diff --git a/src/pylorax/api/v0.py b/src/pylorax/api/v0.py index 4458c800..68b1604d 100644 --- a/src/pylorax/api/v0.py +++ b/src/pylorax/api/v0.py @@ -736,6 +736,54 @@ DELETE `/api/v0/compose/delete/` ] } +`/api/v0/compose/info/` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get detailed information about the compose. The returned JSON string will + contain the following information: + + * id - The uuid of the comoposition + * config - containing the configuration settings used to run Anaconda + * recipe - The depsolved recipe used to generate the kickstart + * commit - The (local) git commit hash for the recipe used + * deps - The NEVRA of all of the dependencies used in the composition + * compose_type - The type of output generated (tar, iso, etc.) + * queue_status - The final status of the composition (FINISHED or FAILED) + + Example:: + + { + "commit": "7078e521a54b12eae31c3fd028680da7a0815a4d", + "compose_type": "tar", + "config": { + "anaconda_args": "", + "armplatform": "", + "compress_args": [], + "compression": "xz", + "image_name": "root.tar.xz", + ... + }, + "deps": { + "packages": [ + { + "arch": "x86_64", + "epoch": "0", + "name": "acl", + "release": "14.el7", + "version": "2.2.51" + } + ] + }, + "id": "c30b7d80-523b-4a23-ad52-61b799739ce8", + "queue_status": "FINISHED", + "recipe": { + "description": "An example kubernetes master", + ... + } + } + + + """ import logging @@ -747,7 +795,7 @@ from pylorax.api.compose import start_build, compose_types from pylorax.api.crossdomain import crossdomain from pylorax.api.projects import projects_list, projects_info, projects_depsolve from pylorax.api.projects import modules_list, modules_info, ProjectsError -from pylorax.api.queue import queue_status, build_status, uuid_delete, uuid_status +from pylorax.api.queue import queue_status, build_status, uuid_delete, uuid_status, uuid_info from pylorax.api.recipes import list_branch_files, read_recipe_commit, recipe_filename, list_commits from pylorax.api.recipes import recipe_from_dict, recipe_from_toml, commit_recipe, delete_recipe, revert_recipe from pylorax.api.recipes import tag_recipe_commit, recipe_diff @@ -1231,15 +1279,9 @@ def v0_api(api): if errors: return jsonify(status=False, error={"msg":"\n".join(errors)}), 400 - # Get the git version (if it exists) try: - with api.config["GITLOCK"].lock: - recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) - except Exception as e: - log.error("Problem reading recipe %s: %s", recipe_name, str(e)) - return jsonify(status=False, error={"msg":str(e)}), 400 - try: - build_id = start_build(api.config["COMPOSER_CFG"], api.config["YUMLOCK"], recipe, compose_type) + build_id = start_build(api.config["COMPOSER_CFG"], api.config["YUMLOCK"], api.config["GITLOCK"], + branch, recipe_name, compose_type) except Exception as e: return jsonify(status=False, error={"msg":str(e)}), 400 @@ -1303,3 +1345,14 @@ def v0_api(api): else: results.append({"uuid":uuid, "status":True}) return jsonify(uuids=results, errors=errors) + + @api.route("/api/v0/compose/info/") + @crossdomain(origin="*") + def v0_compose_info(uuid): + """Return detailed info about a compose""" + try: + info = uuid_info(api.config["COMPOSER_CFG"], uuid) + except Exception as e: + return jsonify(status=False, msg=str(e)) + + return jsonify(**info)