Convert errors to JSON

Signed-off-by: Matt Prahl <mprahl@redhat.com>
Signed-off-by: Nils Philippsen <nils@redhat.com>
This commit is contained in:
Matt Prahl
2016-08-24 15:21:44 -04:00
committed by Nils Philippsen
parent d809d34ed7
commit fbe1beee54
4 changed files with 115 additions and 46 deletions

View File

@@ -22,6 +22,7 @@
# SOFTWARE.
#
# Written by Petr Šabata <contyk@redhat.com>
# Matt Prahl <mprahl@redhat.com>
"""The module build orchestrator for Modularity.
@@ -45,6 +46,8 @@ from flask_sqlalchemy import SQLAlchemy
from os import sys
import rida.logger
from logging import getLogger
from rida.errors import (ValidationError, Unauthorized, UnprocessableEntity,
Conflict, NotFound, Forbidden, json_error)
app = Flask(__name__)
app.config.from_envvar("RIDA_SETTINGS", silent=True)
@@ -58,6 +61,47 @@ else:
db = SQLAlchemy(app)
@app.errorhandler(ValidationError)
def validationerror_error(e):
"""Flask error handler for ValidationError exceptions"""
return json_error(400, 'Bad Request', e.args[0])
@app.errorhandler(Unauthorized)
def unauthorized_error(e):
"""Flask error handler for NotAuthorized exceptions"""
return json_error(401, 'Unauthorized', e.args[0])
@app.errorhandler(Forbidden)
def forbidden_error(e):
"""Flask error handler for Forbidden exceptions"""
return json_error(403, 'Forbidden', e.args[0])
@app.errorhandler(RuntimeError)
def runtimeerror_error(e):
"""Flask error handler for RuntimeError exceptions"""
return json_error(500, 'Internal Server Error', e.args[0])
@app.errorhandler(UnprocessableEntity)
def unprocessableentity_error(e):
"""Flask error handler for UnprocessableEntity exceptions"""
return json_error(422, 'Unprocessable Entity', e.args[0])
@app.errorhandler(Conflict)
def conflict_error(e):
"""Flask error handler for Conflict exceptions"""
return json_error(409, 'Conflict', e.args[0])
@app.errorhandler(NotFound)
def notfound_error(e):
"""Flask error handler for Conflict exceptions"""
return json_error(404, 'Not Found', e.args[0])
import rida.config
conf = rida.config.from_app_config()
rida.logger.init_logging(conf)

View File

@@ -20,6 +20,37 @@
#
# Written by Matt Prahl <mprahl@redhat.com>
""" Defines custom exceptions and error handling functions """
from flask import jsonify
class ValidationError(ValueError):
pass
class Unauthorized(ValueError):
pass
class Forbidden(ValueError):
pass
class UnprocessableEntity(ValueError):
pass
class Conflict(ValueError):
pass
class NotFound(ValueError):
pass
def json_error(status, error, message):
response = jsonify(
{'status': status,
'error': error,
'message': message})
response.status_code = status
return response

View File

@@ -36,6 +36,7 @@ import tempfile
import shutil
from rida import log
from rida.errors import Unauthorized, ValidationError
import rida.utils
@@ -62,7 +63,7 @@ class SCM(object):
:param str url: The unmodified scmurl
:param list allowed_scm: The list of allowed SCMs, optional
:raises: RuntimeError
:raises: Unauthorized or ValidationError
"""
if allowed_scm:
@@ -70,7 +71,8 @@ class SCM(object):
if url.startswith(allowed):
break
else:
raise RuntimeError('%s is not in the list of allowed SCMs' % url)
raise Unauthorized(
'%s is not in the list of allowed SCMs' % url)
self.url = url
@@ -79,7 +81,7 @@ class SCM(object):
self.scheme = scmtype
break
else:
raise RuntimeError('Invalid SCM URL: %s' % url)
raise ValidationError('Invalid SCM URL: %s' % url)
if self.scheme == "git":
match = re.search(r"^(?P<repository>.*/(?P<name>[^?]*))(\?#(?P<commit>.*))?", url)
@@ -89,7 +91,7 @@ class SCM(object):
self.name = self.name[:-4]
self.commit = match.group("commit")
else:
raise RuntimeError("Unhandled SCM scheme: %s" % self.scheme)
raise ValidationError("Unhandled SCM scheme: %s" % self.scheme)
@staticmethod
@rida.utils.retry(wait_on=RuntimeError)

View File

@@ -40,7 +40,8 @@ import tempfile
from rida import app, conf, db, log
from rida import models
from rida.utils import pagination_metadata, filter_module_builds
from errors import ValidationError
from errors import (ValidationError, Unauthorized, UnprocessableEntity,
Conflict, NotFound)
@app.route("/rida/module-builds/", methods=["POST"])
@@ -48,15 +49,16 @@ def submit_build():
"""Handles new module build submissions."""
username = rida.auth.is_packager(conf.pkgdb_api_url)
if not username:
return "You must use your Fedora certificate when submitting a new build", 403
raise Unauthorized("You must use your Fedora certificate "
"when submitting a new build")
try:
r = json.loads(request.get_data().decode("utf-8"))
except:
return "Invalid JSON submitted", 400
raise ValidationError('Invalid JSON submitted')
if "scmurl" not in r:
return "Missing scmurl", 400
raise ValidationError('Missing scmurl')
url = r["scmurl"]
urlallowed = False
@@ -68,7 +70,7 @@ def submit_build():
break
if not urlallowed:
return "The submitted scmurl isn't allowed", 403
raise Unauthorized('The submitted scmurl isn\'t allowed')
yaml = str()
td = None
@@ -80,14 +82,6 @@ def submit_build():
with open(cofn, "r") as mmdfile:
yaml = mmdfile.read()
except Exception as e:
if "is not in the list of allowed SCMs" in str(e):
rc = 403
elif "Invalid SCM URL" in str(e):
rc = 400
else:
rc = 500
return str(e), rc
finally:
try:
if td is not None:
@@ -101,10 +95,10 @@ def submit_build():
try:
mmd.loads(yaml)
except:
return "Invalid modulemd", 422
raise UnprocessableEntity('Invalid modulemd')
if models.ModuleBuild.query.filter_by(name=mmd.name, version=mmd.version, release=mmd.release).first():
return "Module already exists", 409
raise Conflict('Module already exists')
module = models.ModuleBuild.create(
db.session,
@@ -117,33 +111,34 @@ def submit_build():
username=username
)
def failure(message, code):
# TODO, we should make some note of why it failed in the db..
log.exception(message)
module.transition(conf, models.BUILD_STATES["failed"])
db.session.add(module)
db.session.commit()
return message, code
for pkgname, pkg in mmd.components.rpms.packages.items():
if pkg.get("repository") and not conf.rpms_allow_repository:
return failure("Custom component repositories aren't allowed", 403)
if pkg.get("cache") and not conf.rpms_allow_cache:
return failure("Custom component caches aren't allowed", 403)
if not pkg.get("repository"):
pkg["repository"] = conf.rpms_default_repository + pkgname
if not pkg.get("cache"):
pkg["cache"] = conf.rpms_default_cache + pkgname
if not pkg.get("commit"):
try:
pkg["commit"] = rida.scm.SCM(pkg["repository"]).get_latest()
except Exception as e:
return failure("Failed to get the latest commit: %s" % pkgname, 422)
try:
if pkg.get("repository") and not conf.rpms_allow_repository:
raise Unauthorized(
"Custom component repositories aren't allowed")
if pkg.get("cache") and not conf.rpms_allow_cache:
raise Unauthorized("Custom component caches aren't allowed")
if not pkg.get("repository"):
pkg["repository"] = conf.rpms_default_repository + pkgname
if not pkg.get("cache"):
pkg["cache"] = conf.rpms_default_cache + pkgname
if not pkg.get("commit"):
try:
pkg["commit"] = rida.scm.SCM(
pkg["repository"]).get_latest()
except Exception as e:
raise UnprocessableEntity(
"Failed to get the latest commit: %s" % pkgname)
except Exception:
module.transition(conf, models.BUILD_STATES["failed"])
db.session.add(module)
db.session.commit()
raise
full_url = pkg["repository"] + "?#" + pkg["commit"]
if not rida.scm.SCM(full_url).is_available():
return failure("Cannot checkout %s" % pkgname, 422)
raise UnprocessableEntity("Cannot checkout %s" % pkgname)
build = models.ComponentBuild(
module_id=module.id,
@@ -165,10 +160,7 @@ def submit_build():
@app.route("/rida/module-builds/", methods=["GET"])
def query_builds():
"""Lists all tracked module builds."""
try:
p_query = filter_module_builds(request)
except ValidationError as e:
return e.message, 400
p_query = filter_module_builds(request)
json_data = {
'meta': pagination_metadata(p_query)
@@ -192,4 +184,4 @@ def query_build(id):
if module:
return jsonify(module.api_json()), 200
else:
return "No such module found.", 404
raise NotFound('No such module found.')