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)
This commit is contained in:
parent
d3a9ec3002
commit
ed03ac7524
@ -41,6 +41,7 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from pylorax.api.projects import projects_depsolve, dep_nevra
|
from pylorax.api.projects import projects_depsolve, dep_nevra
|
||||||
from pylorax.api.projects import ProjectsError
|
from pylorax.api.projects import ProjectsError
|
||||||
|
from pylorax.api.recipes import read_recipe_and_id
|
||||||
from pylorax.imgutils import default_image_name
|
from pylorax.imgutils import default_image_name
|
||||||
from pylorax.sysutils import joinpaths
|
from pylorax.sysutils import joinpaths
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ def repo_to_ks(r, url="url"):
|
|||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def start_build(cfg, yumlock, recipe, compose_type):
|
def start_build(cfg, yumlock, gitlock, branch, recipe_name, compose_type):
|
||||||
""" Start the build
|
""" Start the build
|
||||||
|
|
||||||
:param cfg: Configuration object
|
: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):
|
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)))
|
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
|
# Combine modules and packages and depsolve the list
|
||||||
# TODO include the version/glob in the depsolving
|
# TODO include the version/glob in the depsolving
|
||||||
module_names = map(lambda m: m["name"], recipe["modules"] or [])
|
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)
|
results_dir = joinpaths(lib_dir, "results", build_id)
|
||||||
os.makedirs(results_dir)
|
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
|
# Write the original recipe
|
||||||
recipe_path = joinpaths(results_dir, "recipe.toml")
|
recipe_path = joinpaths(results_dir, "recipe.toml")
|
||||||
with open(recipe_path, "w") as f:
|
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()
|
ks_template = open(ks_template_path, "r").read()
|
||||||
|
|
||||||
# Write out the dependencies to the results dir
|
# 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:
|
with open(deps_path, "w") as f:
|
||||||
for d in deps:
|
f.write(toml.dumps({"packages":deps}).encode("UTF-8"))
|
||||||
f.write(dep_nevra(d)+"\n")
|
|
||||||
|
|
||||||
# Create the final kickstart with repos and package list
|
# Create the final kickstart with repos and package list
|
||||||
ks_path = joinpaths(results_dir, "final-kickstart.ks")
|
ks_path = joinpaths(results_dir, "final-kickstart.ks")
|
||||||
|
@ -122,6 +122,17 @@ def make_compose(cfg, results_dir):
|
|||||||
log.debug("Install finished, chowning results to %s:%s", user, group)
|
log.debug("Install finished, chowning results to %s:%s", user, group)
|
||||||
subprocess.call(["chown", "-R", "%s:%s" % (user, group), results_dir])
|
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):
|
def compose_detail(results_dir):
|
||||||
""" Return details about the build."""
|
""" Return details about the build."""
|
||||||
|
|
||||||
@ -134,17 +145,14 @@ def compose_detail(results_dir):
|
|||||||
mtime = os.stat(joinpaths(results_dir, "STATUS")).st_mtime
|
mtime = os.stat(joinpaths(results_dir, "STATUS")).st_mtime
|
||||||
recipe = recipe_from_file(joinpaths(results_dir, "recipe.toml"))
|
recipe = recipe_from_file(joinpaths(results_dir, "recipe.toml"))
|
||||||
|
|
||||||
# Should only be 2 kickstarts, the final-kickstart.ks and the template
|
compose_type = get_compose_type(results_dir)
|
||||||
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)
|
|
||||||
|
|
||||||
return {"id": build_id,
|
return {"id": build_id,
|
||||||
"status": status,
|
"queue_status": status,
|
||||||
"timestamp":mtime,
|
"timestamp": mtime,
|
||||||
"recipe": recipe["name"],
|
"compose_type": compose_type,
|
||||||
"version": recipe["version"]
|
"recipe": recipe["name"],
|
||||||
|
"version": recipe["version"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def queue_status(cfg):
|
def queue_status(cfg):
|
||||||
@ -218,3 +226,58 @@ def uuid_delete(cfg, uuid):
|
|||||||
raise RuntimeError("Directory length is too short: %s" % uuid_dir)
|
raise RuntimeError("Directory length is too short: %s" % uuid_dir)
|
||||||
shutil.rmtree(uuid_dir)
|
shutil.rmtree(uuid_dir)
|
||||||
return True
|
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
|
||||||
|
}
|
||||||
|
@ -736,6 +736,54 @@ DELETE `/api/v0/compose/delete/<uuids>`
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
`/api/v0/compose/info/<uuid>`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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
|
import logging
|
||||||
@ -747,7 +795,7 @@ from pylorax.api.compose import start_build, compose_types
|
|||||||
from pylorax.api.crossdomain import crossdomain
|
from pylorax.api.crossdomain import crossdomain
|
||||||
from pylorax.api.projects import projects_list, projects_info, projects_depsolve
|
from pylorax.api.projects import projects_list, projects_info, projects_depsolve
|
||||||
from pylorax.api.projects import modules_list, modules_info, ProjectsError
|
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 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 recipe_from_dict, recipe_from_toml, commit_recipe, delete_recipe, revert_recipe
|
||||||
from pylorax.api.recipes import tag_recipe_commit, recipe_diff
|
from pylorax.api.recipes import tag_recipe_commit, recipe_diff
|
||||||
@ -1231,15 +1279,9 @@ def v0_api(api):
|
|||||||
if errors:
|
if errors:
|
||||||
return jsonify(status=False, error={"msg":"\n".join(errors)}), 400
|
return jsonify(status=False, error={"msg":"\n".join(errors)}), 400
|
||||||
|
|
||||||
# Get the git version (if it exists)
|
|
||||||
try:
|
try:
|
||||||
with api.config["GITLOCK"].lock:
|
build_id = start_build(api.config["COMPOSER_CFG"], api.config["YUMLOCK"], api.config["GITLOCK"],
|
||||||
recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name)
|
branch, recipe_name, compose_type)
|
||||||
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)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify(status=False, error={"msg":str(e)}), 400
|
return jsonify(status=False, error={"msg":str(e)}), 400
|
||||||
|
|
||||||
@ -1303,3 +1345,14 @@ def v0_api(api):
|
|||||||
else:
|
else:
|
||||||
results.append({"uuid":uuid, "status":True})
|
results.append({"uuid":uuid, "status":True})
|
||||||
return jsonify(uuids=results, errors=errors)
|
return jsonify(uuids=results, errors=errors)
|
||||||
|
|
||||||
|
@api.route("/api/v0/compose/info/<uuid>")
|
||||||
|
@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)
|
||||||
|
Loading…
Reference in New Issue
Block a user