Update the /api/v0/ route handling to use the flask_blueprints Blueprint class

Instead of setting up the routes inside a function we can now use a
BlueprintSkip class, which allows us to register them at different
routes (eg. /api/v0/ and /api/v1/) and override any routes that will be
replaced by the new API version.
This commit is contained in:
Brian C. Lane 2019-04-22 14:59:02 -07:00
parent 7071e62985
commit 6d50a5874e
2 changed files with 886 additions and 882 deletions

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2017 Red Hat, Inc. # Copyright (C) 2017-2019 Red Hat, Inc.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -83,4 +83,5 @@ def v0_status():
def bad_request(error): def bad_request(error):
return jsonify(status=False, errors=[{ "id": HTTP_ERROR, "code": error.code, "msg": error.name }]), error.code return jsonify(status=False, errors=[{ "id": HTTP_ERROR, "code": error.code, "msg": error.name }]), error.code
v0_api(server) # Register the v0 API on /api/v0/
server.register_blueprint(v0_api, url_prefix="/api/v0/")

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2017-2018 Red Hat, Inc. # Copyright (C) 2017-2019 Red Hat, Inc.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -993,12 +993,14 @@ log = logging.getLogger("lorax-composer")
import os import os
from flask import jsonify, request, Response, send_file from flask import jsonify, request, Response, send_file
from flask import current_app as api
import pytoml as toml import pytoml as toml
from pylorax.sysutils import joinpaths from pylorax.sysutils import joinpaths
from pylorax.api.checkparams import checkparams from pylorax.api.checkparams import checkparams
from pylorax.api.compose import start_build, compose_types from pylorax.api.compose import start_build, compose_types
from pylorax.api.errors import * # pylint: disable=wildcard-import from pylorax.api.errors import * # pylint: disable=wildcard-import
from pylorax.api.flask_blueprint import BlueprintSkip
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, repo_to_source from pylorax.api.projects import modules_list, modules_info, ProjectsError, repo_to_source
from pylorax.api.projects import get_repo_sources, delete_repo_source, source_to_repo, dnf_repo_to_file_repo from pylorax.api.projects import get_repo_sources, delete_repo_source, source_to_repo, dnf_repo_to_file_repo
@ -1026,7 +1028,7 @@ def take_limits(iterable, offset, limit):
""" """
return iterable[offset:][:limit] return iterable[offset:][:limit]
def blueprint_exists(api, branch, blueprint_name): def blueprint_exists(branch, blueprint_name):
"""Return True if the blueprint exists """Return True if the blueprint exists
:param api: flask object :param api: flask object
@ -1044,10 +1046,11 @@ def blueprint_exists(api, branch, blueprint_name):
except (RecipeError, RecipeFileError): except (RecipeError, RecipeFileError):
return False return False
def v0_api(api): # Create the v0 routes Blueprint with skip_routes support
# Note that Sphinx will not generate documentations for any of these. v0_api = BlueprintSkip("v0_routes", __name__)
@api.route("/api/v0/blueprints/list")
def v0_blueprints_list(): @v0_api.route("/blueprints/list")
def v0_blueprints_list():
"""List the available blueprints on a branch.""" """List the available blueprints on a branch."""
branch = request.args.get("branch", "master") branch = request.args.get("branch", "master")
if VALID_API_STRING.match(branch) is None: if VALID_API_STRING.match(branch) is None:
@ -1064,10 +1067,10 @@ def v0_api(api):
limited_blueprints = take_limits(blueprints, offset, limit) limited_blueprints = take_limits(blueprints, offset, limit)
return jsonify(blueprints=limited_blueprints, limit=limit, offset=offset, total=len(blueprints)) return jsonify(blueprints=limited_blueprints, limit=limit, offset=offset, total=len(blueprints))
@api.route("/api/v0/blueprints/info", defaults={'blueprint_names': ""}) @v0_api.route("/blueprints/info", defaults={'blueprint_names': ""})
@api.route("/api/v0/blueprints/info/<blueprint_names>") @v0_api.route("/blueprints/info/<blueprint_names>")
@checkparams([("blueprint_names", "", "no blueprint names given")]) @checkparams([("blueprint_names", "", "no blueprint names given")])
def v0_blueprints_info(blueprint_names): def v0_blueprints_info(blueprint_names):
"""Return the contents of the blueprint, or a list of blueprints""" """Return the contents of the blueprint, or a list of blueprints"""
if VALID_API_STRING.match(blueprint_names) is None: if VALID_API_STRING.match(blueprint_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1137,10 +1140,10 @@ def v0_api(api):
else: else:
return jsonify(changes=changes, blueprints=blueprints, errors=errors) return jsonify(changes=changes, blueprints=blueprints, errors=errors)
@api.route("/api/v0/blueprints/changes", defaults={'blueprint_names': ""}) @v0_api.route("/blueprints/changes", defaults={'blueprint_names': ""})
@api.route("/api/v0/blueprints/changes/<blueprint_names>") @v0_api.route("/blueprints/changes/<blueprint_names>")
@checkparams([("blueprint_names", "", "no blueprint names given")]) @checkparams([("blueprint_names", "", "no blueprint names given")])
def v0_blueprints_changes(blueprint_names): def v0_blueprints_changes(blueprint_names):
"""Return the changes to a blueprint or list of blueprints""" """Return the changes to a blueprint or list of blueprints"""
if VALID_API_STRING.match(blueprint_names) is None: if VALID_API_STRING.match(blueprint_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1177,8 +1180,8 @@ def v0_api(api):
return jsonify(blueprints=blueprints, errors=errors, offset=offset, limit=limit) return jsonify(blueprints=blueprints, errors=errors, offset=offset, limit=limit)
@api.route("/api/v0/blueprints/new", methods=["POST"]) @v0_api.route("/blueprints/new", methods=["POST"])
def v0_blueprints_new(): def v0_blueprints_new():
"""Commit a new blueprint""" """Commit a new blueprint"""
branch = request.args.get("branch", "master") branch = request.args.get("branch", "master")
if VALID_API_STRING.match(branch) is None: if VALID_API_STRING.match(branch) is None:
@ -1205,10 +1208,10 @@ def v0_api(api):
else: else:
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/blueprints/delete", defaults={'blueprint_name': ""}, methods=["DELETE"]) @v0_api.route("/blueprints/delete", defaults={'blueprint_name': ""}, methods=["DELETE"])
@api.route("/api/v0/blueprints/delete/<blueprint_name>", methods=["DELETE"]) @v0_api.route("/blueprints/delete/<blueprint_name>", methods=["DELETE"])
@checkparams([("blueprint_name", "", "no blueprint name given")]) @checkparams([("blueprint_name", "", "no blueprint name given")])
def v0_blueprints_delete(blueprint_name): def v0_blueprints_delete(blueprint_name):
"""Delete a blueprint from git""" """Delete a blueprint from git"""
if VALID_API_STRING.match(blueprint_name) is None: if VALID_API_STRING.match(blueprint_name) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1227,8 +1230,8 @@ def v0_api(api):
else: else:
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/blueprints/workspace", methods=["POST"]) @v0_api.route("/blueprints/workspace", methods=["POST"])
def v0_blueprints_workspace(): def v0_blueprints_workspace():
"""Write a blueprint to the workspace""" """Write a blueprint to the workspace"""
branch = request.args.get("branch", "master") branch = request.args.get("branch", "master")
if VALID_API_STRING.match(branch) is None: if VALID_API_STRING.match(branch) is None:
@ -1251,10 +1254,10 @@ def v0_api(api):
else: else:
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/blueprints/workspace", defaults={'blueprint_name': ""}, methods=["DELETE"]) @v0_api.route("/blueprints/workspace", defaults={'blueprint_name': ""}, methods=["DELETE"])
@api.route("/api/v0/blueprints/workspace/<blueprint_name>", methods=["DELETE"]) @v0_api.route("/blueprints/workspace/<blueprint_name>", methods=["DELETE"])
@checkparams([("blueprint_name", "", "no blueprint name given")]) @checkparams([("blueprint_name", "", "no blueprint name given")])
def v0_blueprints_delete_workspace(blueprint_name): def v0_blueprints_delete_workspace(blueprint_name):
"""Delete a blueprint from the workspace""" """Delete a blueprint from the workspace"""
if VALID_API_STRING.match(blueprint_name) is None: if VALID_API_STRING.match(blueprint_name) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1272,12 +1275,12 @@ def v0_api(api):
else: else:
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/blueprints/undo", defaults={'blueprint_name': "", 'commit': ""}, methods=["POST"]) @v0_api.route("/blueprints/undo", defaults={'blueprint_name': "", 'commit': ""}, methods=["POST"])
@api.route("/api/v0/blueprints/undo/<blueprint_name>", defaults={'commit': ""}, methods=["POST"]) @v0_api.route("/blueprints/undo/<blueprint_name>", defaults={'commit': ""}, methods=["POST"])
@api.route("/api/v0/blueprints/undo/<blueprint_name>/<commit>", methods=["POST"]) @v0_api.route("/blueprints/undo/<blueprint_name>/<commit>", methods=["POST"])
@checkparams([("blueprint_name", "", "no blueprint name given"), @checkparams([("blueprint_name", "", "no blueprint name given"),
("commit", "", "no commit ID given")]) ("commit", "", "no commit ID given")])
def v0_blueprints_undo(blueprint_name, commit): def v0_blueprints_undo(blueprint_name, commit):
"""Undo changes to a blueprint by reverting to a previous commit.""" """Undo changes to a blueprint by reverting to a previous commit."""
if VALID_API_STRING.match(blueprint_name) is None: if VALID_API_STRING.match(blueprint_name) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1299,10 +1302,10 @@ def v0_api(api):
else: else:
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/blueprints/tag", defaults={'blueprint_name': ""}, methods=["POST"]) @v0_api.route("/blueprints/tag", defaults={'blueprint_name': ""}, methods=["POST"])
@api.route("/api/v0/blueprints/tag/<blueprint_name>", methods=["POST"]) @v0_api.route("/blueprints/tag/<blueprint_name>", methods=["POST"])
@checkparams([("blueprint_name", "", "no blueprint name given")]) @checkparams([("blueprint_name", "", "no blueprint name given")])
def v0_blueprints_tag(blueprint_name): def v0_blueprints_tag(blueprint_name):
"""Tag a blueprint's latest blueprint commit as a 'revision'""" """Tag a blueprint's latest blueprint commit as a 'revision'"""
if VALID_API_STRING.match(blueprint_name) is None: if VALID_API_STRING.match(blueprint_name) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1323,14 +1326,14 @@ def v0_api(api):
else: else:
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/blueprints/diff", defaults={'blueprint_name': "", 'from_commit': "", 'to_commit': ""}) @v0_api.route("/blueprints/diff", defaults={'blueprint_name': "", 'from_commit': "", 'to_commit': ""})
@api.route("/api/v0/blueprints/diff/<blueprint_name>", defaults={'from_commit': "", 'to_commit': ""}) @v0_api.route("/blueprints/diff/<blueprint_name>", defaults={'from_commit': "", 'to_commit': ""})
@api.route("/api/v0/blueprints/diff/<blueprint_name>/<from_commit>", defaults={'to_commit': ""}) @v0_api.route("/blueprints/diff/<blueprint_name>/<from_commit>", defaults={'to_commit': ""})
@api.route("/api/v0/blueprints/diff/<blueprint_name>/<from_commit>/<to_commit>") @v0_api.route("/blueprints/diff/<blueprint_name>/<from_commit>/<to_commit>")
@checkparams([("blueprint_name", "", "no blueprint name given"), @checkparams([("blueprint_name", "", "no blueprint name given"),
("from_commit", "", "no from commit ID given"), ("from_commit", "", "no from commit ID given"),
("to_commit", "", "no to commit ID given")]) ("to_commit", "", "no to commit ID given")])
def v0_blueprints_diff(blueprint_name, from_commit, to_commit): def v0_blueprints_diff(blueprint_name, from_commit, to_commit):
"""Return the differences between two commits of a blueprint""" """Return the differences between two commits of a blueprint"""
for s in [blueprint_name, from_commit, to_commit]: for s in [blueprint_name, from_commit, to_commit]:
if VALID_API_STRING.match(s) is None: if VALID_API_STRING.match(s) is None:
@ -1340,7 +1343,7 @@ def v0_api(api):
if VALID_API_STRING.match(branch) is None: if VALID_API_STRING.match(branch) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in branch argument"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in branch argument"}]), 400
if not blueprint_exists(api, branch, blueprint_name): if not blueprint_exists(branch, blueprint_name):
return jsonify(status=False, errors=[{"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name}]) return jsonify(status=False, errors=[{"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name}])
try: try:
@ -1375,10 +1378,10 @@ def v0_api(api):
diff = recipe_diff(old_blueprint, new_blueprint) diff = recipe_diff(old_blueprint, new_blueprint)
return jsonify(diff=diff) return jsonify(diff=diff)
@api.route("/api/v0/blueprints/freeze", defaults={'blueprint_names': ""}) @v0_api.route("/blueprints/freeze", defaults={'blueprint_names': ""})
@api.route("/api/v0/blueprints/freeze/<blueprint_names>") @v0_api.route("/blueprints/freeze/<blueprint_names>")
@checkparams([("blueprint_names", "", "no blueprint names given")]) @checkparams([("blueprint_names", "", "no blueprint names given")])
def v0_blueprints_freeze(blueprint_names): def v0_blueprints_freeze(blueprint_names):
"""Return the blueprint with the exact modules and packages selected by depsolve""" """Return the blueprint with the exact modules and packages selected by depsolve"""
if VALID_API_STRING.match(blueprint_names) is None: if VALID_API_STRING.match(blueprint_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1441,10 +1444,10 @@ def v0_api(api):
else: else:
return jsonify(blueprints=blueprints, errors=errors) return jsonify(blueprints=blueprints, errors=errors)
@api.route("/api/v0/blueprints/depsolve", defaults={'blueprint_names': ""}) @v0_api.route("/blueprints/depsolve", defaults={'blueprint_names': ""})
@api.route("/api/v0/blueprints/depsolve/<blueprint_names>") @v0_api.route("/blueprints/depsolve/<blueprint_names>")
@checkparams([("blueprint_names", "", "no blueprint names given")]) @checkparams([("blueprint_names", "", "no blueprint names given")])
def v0_blueprints_depsolve(blueprint_names): def v0_blueprints_depsolve(blueprint_names):
"""Return the dependencies for a blueprint""" """Return the dependencies for a blueprint"""
if VALID_API_STRING.match(blueprint_names) is None: if VALID_API_STRING.match(blueprint_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1506,8 +1509,8 @@ def v0_api(api):
return jsonify(blueprints=blueprints, errors=errors) return jsonify(blueprints=blueprints, errors=errors)
@api.route("/api/v0/projects/list") @v0_api.route("/projects/list")
def v0_projects_list(): def v0_projects_list():
"""List all of the available projects/packages""" """List all of the available projects/packages"""
try: try:
limit = int(request.args.get("limit", "20")) limit = int(request.args.get("limit", "20"))
@ -1525,10 +1528,10 @@ def v0_api(api):
projects = take_limits(available, offset, limit) projects = take_limits(available, offset, limit)
return jsonify(projects=projects, offset=offset, limit=limit, total=len(available)) return jsonify(projects=projects, offset=offset, limit=limit, total=len(available))
@api.route("/api/v0/projects/info", defaults={'project_names': ""}) @v0_api.route("/projects/info", defaults={'project_names': ""})
@api.route("/api/v0/projects/info/<project_names>") @v0_api.route("/projects/info/<project_names>")
@checkparams([("project_names", "", "no project names given")]) @checkparams([("project_names", "", "no project names given")])
def v0_projects_info(project_names): def v0_projects_info(project_names):
"""Return detailed information about the listed projects""" """Return detailed information about the listed projects"""
if VALID_API_STRING.match(project_names) is None: if VALID_API_STRING.match(project_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1547,10 +1550,10 @@ def v0_api(api):
return jsonify(projects=projects) return jsonify(projects=projects)
@api.route("/api/v0/projects/depsolve", defaults={'project_names': ""}) @v0_api.route("/projects/depsolve", defaults={'project_names': ""})
@api.route("/api/v0/projects/depsolve/<project_names>") @v0_api.route("/projects/depsolve/<project_names>")
@checkparams([("project_names", "", "no project names given")]) @checkparams([("project_names", "", "no project names given")])
def v0_projects_depsolve(project_names): def v0_projects_depsolve(project_names):
"""Return detailed information about the listed projects""" """Return detailed information about the listed projects"""
if VALID_API_STRING.match(project_names) is None: if VALID_API_STRING.match(project_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1569,18 +1572,18 @@ def v0_api(api):
return jsonify(projects=deps) return jsonify(projects=deps)
@api.route("/api/v0/projects/source/list") @v0_api.route("/projects/source/list")
def v0_projects_source_list(): def v0_projects_source_list():
"""Return the list of source names""" """Return the list of source names"""
with api.config["DNFLOCK"].lock: with api.config["DNFLOCK"].lock:
repos = list(api.config["DNFLOCK"].dbo.repos.iter_enabled()) repos = list(api.config["DNFLOCK"].dbo.repos.iter_enabled())
sources = sorted([r.id for r in repos]) sources = sorted([r.id for r in repos])
return jsonify(sources=sources) return jsonify(sources=sources)
@api.route("/api/v0/projects/source/info", defaults={'source_names': ""}) @v0_api.route("/projects/source/info", defaults={'source_names': ""})
@api.route("/api/v0/projects/source/info/<source_names>") @v0_api.route("/projects/source/info/<source_names>")
@checkparams([("source_names", "", "no source names given")]) @checkparams([("source_names", "", "no source names given")])
def v0_projects_source_info(source_names): def v0_projects_source_info(source_names):
"""Return detailed info about the list of sources""" """Return detailed info about the list of sources"""
if VALID_API_STRING.match(source_names) is None: if VALID_API_STRING.match(source_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1614,8 +1617,8 @@ def v0_api(api):
else: else:
return jsonify(sources=sources, errors=errors) return jsonify(sources=sources, errors=errors)
@api.route("/api/v0/projects/source/new", methods=["POST"]) @v0_api.route("/projects/source/new", methods=["POST"])
def v0_projects_source_new(): def v0_projects_source_new():
"""Add a new package source. Or change an existing one""" """Add a new package source. Or change an existing one"""
if request.headers['Content-Type'] == "text/x-toml": if request.headers['Content-Type'] == "text/x-toml":
source = toml.loads(request.data) source = toml.loads(request.data)
@ -1673,10 +1676,10 @@ def v0_api(api):
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/projects/source/delete", defaults={'source_name': ""}, methods=["DELETE"]) @v0_api.route("/projects/source/delete", defaults={'source_name': ""}, methods=["DELETE"])
@api.route("/api/v0/projects/source/delete/<source_name>", methods=["DELETE"]) @v0_api.route("/projects/source/delete/<source_name>", methods=["DELETE"])
@checkparams([("source_name", "", "no source name given")]) @checkparams([("source_name", "", "no source name given")])
def v0_projects_source_delete(source_name): def v0_projects_source_delete(source_name):
"""Delete the named source and return a status response""" """Delete the named source and return a status response"""
if VALID_API_STRING.match(source_name) is None: if VALID_API_STRING.match(source_name) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1703,9 +1706,9 @@ def v0_api(api):
return jsonify(status=True) return jsonify(status=True)
@api.route("/api/v0/modules/list") @v0_api.route("/modules/list")
@api.route("/api/v0/modules/list/<module_names>") @v0_api.route("/modules/list/<module_names>")
def v0_modules_list(module_names=None): def v0_modules_list(module_names=None):
"""List available modules, filtering by module_names""" """List available modules, filtering by module_names"""
if module_names and VALID_API_STRING.match(module_names) is None: if module_names and VALID_API_STRING.match(module_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1734,10 +1737,10 @@ def v0_api(api):
modules = take_limits(available, offset, limit) modules = take_limits(available, offset, limit)
return jsonify(modules=modules, offset=offset, limit=limit, total=len(available)) return jsonify(modules=modules, offset=offset, limit=limit, total=len(available))
@api.route("/api/v0/modules/info", defaults={'module_names': ""}) @v0_api.route("/modules/info", defaults={'module_names': ""})
@api.route("/api/v0/modules/info/<module_names>") @v0_api.route("/modules/info/<module_names>")
@checkparams([("module_names", "", "no module names given")]) @checkparams([("module_names", "", "no module names given")])
def v0_modules_info(module_names): def v0_modules_info(module_names):
"""Return detailed information about the listed modules""" """Return detailed information about the listed modules"""
if VALID_API_STRING.match(module_names) is None: if VALID_API_STRING.match(module_names) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1755,8 +1758,8 @@ def v0_api(api):
return jsonify(modules=modules) return jsonify(modules=modules)
@api.route("/api/v0/compose", methods=["POST"]) @v0_api.route("/compose", methods=["POST"])
def v0_compose_start(): def v0_compose_start():
"""Start a compose """Start a compose
The body of the post should have these fields: The body of the post should have these fields:
@ -1795,7 +1798,7 @@ def v0_api(api):
if VALID_API_STRING.match(blueprint_name) is None: if VALID_API_STRING.match(blueprint_name) is None:
errors.append({"id": INVALID_CHARS, "msg": "Invalid characters in API path"}) errors.append({"id": INVALID_CHARS, "msg": "Invalid characters in API path"})
if not blueprint_exists(api, branch, blueprint_name): if not blueprint_exists(branch, blueprint_name):
errors.append({"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name}) errors.append({"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name})
if errors: if errors:
@ -1812,8 +1815,8 @@ def v0_api(api):
return jsonify(status=True, build_id=build_id) return jsonify(status=True, build_id=build_id)
@api.route("/api/v0/compose/types") @v0_api.route("/compose/types")
def v0_compose_types(): def v0_compose_types():
"""Return the list of enabled output types """Return the list of enabled output types
(only enabled types are returned) (only enabled types are returned)
@ -1821,25 +1824,25 @@ def v0_api(api):
share_dir = api.config["COMPOSER_CFG"].get("composer", "share_dir") share_dir = api.config["COMPOSER_CFG"].get("composer", "share_dir")
return jsonify(types=[{"name": k, "enabled": True} for k in compose_types(share_dir)]) return jsonify(types=[{"name": k, "enabled": True} for k in compose_types(share_dir)])
@api.route("/api/v0/compose/queue") @v0_api.route("/compose/queue")
def v0_compose_queue(): def v0_compose_queue():
"""Return the status of the new and running queues""" """Return the status of the new and running queues"""
return jsonify(queue_status(api.config["COMPOSER_CFG"])) return jsonify(queue_status(api.config["COMPOSER_CFG"]))
@api.route("/api/v0/compose/finished") @v0_api.route("/compose/finished")
def v0_compose_finished(): def v0_compose_finished():
"""Return the list of finished composes""" """Return the list of finished composes"""
return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED")) return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED"))
@api.route("/api/v0/compose/failed") @v0_api.route("/compose/failed")
def v0_compose_failed(): def v0_compose_failed():
"""Return the list of failed composes""" """Return the list of failed composes"""
return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED")) return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED"))
@api.route("/api/v0/compose/status", defaults={'uuids': ""}) @v0_api.route("/compose/status", defaults={'uuids': ""})
@api.route("/api/v0/compose/status/<uuids>") @v0_api.route("/compose/status/<uuids>")
@checkparams([("uuids", "", "no UUIDs given")]) @checkparams([("uuids", "", "no UUIDs given")])
def v0_compose_status(uuids): def v0_compose_status(uuids):
"""Return the status of the listed uuids""" """Return the status of the listed uuids"""
if VALID_API_STRING.match(uuids) is None: if VALID_API_STRING.match(uuids) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1879,10 +1882,10 @@ def v0_api(api):
return jsonify(uuids=results, errors=errors) return jsonify(uuids=results, errors=errors)
@api.route("/api/v0/compose/cancel", defaults={'uuid': ""}, methods=["DELETE"]) @v0_api.route("/compose/cancel", defaults={'uuid': ""}, methods=["DELETE"])
@api.route("/api/v0/compose/cancel/<uuid>", methods=["DELETE"]) @v0_api.route("/compose/cancel/<uuid>", methods=["DELETE"])
@checkparams([("uuid", "", "no UUID given")]) @checkparams([("uuid", "", "no UUID given")])
def v0_compose_cancel(uuid): def v0_compose_cancel(uuid):
"""Cancel a running compose and delete its results directory""" """Cancel a running compose and delete its results directory"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1901,10 +1904,10 @@ def v0_api(api):
else: else:
return jsonify(status=True, uuid=uuid) return jsonify(status=True, uuid=uuid)
@api.route("/api/v0/compose/delete", defaults={'uuids': ""}, methods=["DELETE"]) @v0_api.route("/compose/delete", defaults={'uuids': ""}, methods=["DELETE"])
@api.route("/api/v0/compose/delete/<uuids>", methods=["DELETE"]) @v0_api.route("/compose/delete/<uuids>", methods=["DELETE"])
@checkparams([("uuids", "", "no UUIDs given")]) @checkparams([("uuids", "", "no UUIDs given")])
def v0_compose_delete(uuids): def v0_compose_delete(uuids):
"""Delete the compose results for the listed uuids""" """Delete the compose results for the listed uuids"""
if VALID_API_STRING.match(uuids) is None: if VALID_API_STRING.match(uuids) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1926,10 +1929,10 @@ def v0_api(api):
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", defaults={'uuid': ""}) @v0_api.route("/compose/info", defaults={'uuid': ""})
@api.route("/api/v0/compose/info/<uuid>") @v0_api.route("/compose/info/<uuid>")
@checkparams([("uuid", "", "no UUID given")]) @checkparams([("uuid", "", "no UUID given")])
def v0_compose_info(uuid): def v0_compose_info(uuid):
"""Return detailed info about a compose""" """Return detailed info about a compose"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1944,10 +1947,10 @@ def v0_api(api):
else: else:
return jsonify(**info) return jsonify(**info)
@api.route("/api/v0/compose/metadata", defaults={'uuid': ""}) @v0_api.route("/compose/metadata", defaults={'uuid': ""})
@api.route("/api/v0/compose/metadata/<uuid>") @v0_api.route("/compose/metadata/<uuid>")
@checkparams([("uuid","", "no UUID given")]) @checkparams([("uuid","", "no UUID given")])
def v0_compose_metadata(uuid): def v0_compose_metadata(uuid):
"""Return a tar of the metadata for the build""" """Return a tar of the metadata for the build"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1963,10 +1966,10 @@ def v0_api(api):
headers=[("Content-Disposition", "attachment; filename=%s-metadata.tar;" % uuid)], headers=[("Content-Disposition", "attachment; filename=%s-metadata.tar;" % uuid)],
direct_passthrough=True) direct_passthrough=True)
@api.route("/api/v0/compose/results", defaults={'uuid': ""}) @v0_api.route("/compose/results", defaults={'uuid': ""})
@api.route("/api/v0/compose/results/<uuid>") @v0_api.route("/compose/results/<uuid>")
@checkparams([("uuid","", "no UUID given")]) @checkparams([("uuid","", "no UUID given")])
def v0_compose_results(uuid): def v0_compose_results(uuid):
"""Return a tar of the metadata and the results for the build""" """Return a tar of the metadata and the results for the build"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -1982,10 +1985,10 @@ def v0_api(api):
headers=[("Content-Disposition", "attachment; filename=%s.tar;" % uuid)], headers=[("Content-Disposition", "attachment; filename=%s.tar;" % uuid)],
direct_passthrough=True) direct_passthrough=True)
@api.route("/api/v0/compose/logs", defaults={'uuid': ""}) @v0_api.route("/compose/logs", defaults={'uuid': ""})
@api.route("/api/v0/compose/logs/<uuid>") @v0_api.route("/compose/logs/<uuid>")
@checkparams([("uuid","", "no UUID given")]) @checkparams([("uuid","", "no UUID given")])
def v0_compose_logs(uuid): def v0_compose_logs(uuid):
"""Return a tar of the metadata for the build""" """Return a tar of the metadata for the build"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -2001,10 +2004,10 @@ def v0_api(api):
headers=[("Content-Disposition", "attachment; filename=%s-logs.tar;" % uuid)], headers=[("Content-Disposition", "attachment; filename=%s-logs.tar;" % uuid)],
direct_passthrough=True) direct_passthrough=True)
@api.route("/api/v0/compose/image", defaults={'uuid': ""}) @v0_api.route("/compose/image", defaults={'uuid': ""})
@api.route("/api/v0/compose/image/<uuid>") @v0_api.route("/compose/image/<uuid>")
@checkparams([("uuid","", "no UUID given")]) @checkparams([("uuid","", "no UUID given")])
def v0_compose_image(uuid): def v0_compose_image(uuid):
"""Return the output image for the build""" """Return the output image for the build"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400
@ -2026,10 +2029,10 @@ def v0_api(api):
# XXX - Will mime type guessing work for all our output? # XXX - Will mime type guessing work for all our output?
return send_file(image_path, as_attachment=True, attachment_filename=image_name, add_etags=False) return send_file(image_path, as_attachment=True, attachment_filename=image_name, add_etags=False)
@api.route("/api/v0/compose/log", defaults={'uuid': ""}) @v0_api.route("/compose/log", defaults={'uuid': ""})
@api.route("/api/v0/compose/log/<uuid>") @v0_api.route("/compose/log/<uuid>")
@checkparams([("uuid","", "no UUID given")]) @checkparams([("uuid","", "no UUID given")])
def v0_compose_log_tail(uuid): def v0_compose_log_tail(uuid):
"""Return the end of the main anaconda.log, defaults to 1Mbytes""" """Return the end of the main anaconda.log, defaults to 1Mbytes"""
if VALID_API_STRING.match(uuid) is None: if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400