mirror of
https://pagure.io/fm-orchestrator.git
synced 2026-04-05 11:48:33 +08:00
Prefix API calls with version number (for example '/1/'). Add initial support for multiple versions of API calls.
This commit is contained in:
committed by
Nils Philippsen
parent
806d20c4f2
commit
e236e86fce
260
rida/views.py
260
rida/views.py
@@ -28,6 +28,7 @@ This is the implementation of the orchestrator's public RESTful API.
|
||||
"""
|
||||
|
||||
from flask import request, jsonify
|
||||
from flask.views import View
|
||||
import json
|
||||
import logging
|
||||
import modulemd
|
||||
@@ -43,147 +44,164 @@ from rida.utils import pagination_metadata, filter_module_builds
|
||||
from errors import (ValidationError, Unauthorized, UnprocessableEntity,
|
||||
Conflict, NotFound)
|
||||
|
||||
|
||||
@app.route("/rida/module-builds/", methods=["POST"])
|
||||
def submit_build():
|
||||
class SubmitBuild(View):
|
||||
"""Handles new module build submissions."""
|
||||
|
||||
username = rida.auth.get_username(request.environ)
|
||||
rida.auth.assert_is_packager(username, fas_kwargs=dict(
|
||||
base_url=conf.fas_url,
|
||||
username=conf.fas_username,
|
||||
password=conf.fas_password))
|
||||
def dispatch_request(self):
|
||||
username = rida.auth.get_username(request.environ)
|
||||
rida.auth.assert_is_packager(username, fas_kwargs=dict(
|
||||
base_url=conf.fas_url,
|
||||
username=conf.fas_username,
|
||||
password=conf.fas_password))
|
||||
|
||||
try:
|
||||
r = json.loads(request.get_data().decode("utf-8"))
|
||||
except:
|
||||
raise ValidationError('Invalid JSON submitted')
|
||||
|
||||
if "scmurl" not in r:
|
||||
raise ValidationError('Missing scmurl')
|
||||
|
||||
url = r["scmurl"]
|
||||
urlallowed = False
|
||||
|
||||
for prefix in conf.scmurls:
|
||||
|
||||
if url.startswith(prefix):
|
||||
urlallowed = True
|
||||
break
|
||||
|
||||
if not urlallowed:
|
||||
raise Unauthorized('The submitted scmurl isn\'t allowed')
|
||||
|
||||
yaml = str()
|
||||
td = None
|
||||
try:
|
||||
td = tempfile.mkdtemp()
|
||||
scm = rida.scm.SCM(url, conf.scmurls)
|
||||
cod = scm.checkout(td)
|
||||
cofn = os.path.join(cod, (scm.name + ".yaml"))
|
||||
|
||||
with open(cofn, "r") as mmdfile:
|
||||
yaml = mmdfile.read()
|
||||
finally:
|
||||
try:
|
||||
if td is not None:
|
||||
shutil.rmtree(td)
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
"Failed to remove temporary directory {!r}: {}".format(
|
||||
td, str(e)))
|
||||
r = json.loads(request.get_data().decode("utf-8"))
|
||||
except:
|
||||
raise ValidationError('Invalid JSON submitted')
|
||||
|
||||
mmd = modulemd.ModuleMetadata()
|
||||
try:
|
||||
mmd.loads(yaml)
|
||||
except:
|
||||
raise UnprocessableEntity('Invalid modulemd')
|
||||
if "scmurl" not in r:
|
||||
raise ValidationError('Missing scmurl')
|
||||
|
||||
if models.ModuleBuild.query.filter_by(name=mmd.name, version=mmd.version, release=mmd.release).first():
|
||||
raise Conflict('Module already exists')
|
||||
url = r["scmurl"]
|
||||
if not any(url.startswith(prefix) for prefix in conf.scmurls):
|
||||
raise Unauthorized("The submitted scmurl is not allowed")
|
||||
|
||||
module = models.ModuleBuild.create(
|
||||
db.session,
|
||||
conf,
|
||||
name=mmd.name,
|
||||
version=mmd.version,
|
||||
release=mmd.release,
|
||||
modulemd=yaml,
|
||||
scmurl=url,
|
||||
username=username
|
||||
)
|
||||
|
||||
for pkgname, pkg in mmd.components.rpms.packages.items():
|
||||
yaml = ""
|
||||
td = None
|
||||
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
|
||||
td = tempfile.mkdtemp()
|
||||
scm = rida.scm.SCM(url, conf.scmurls)
|
||||
cod = scm.checkout(td)
|
||||
cofn = os.path.join(cod, (scm.name + ".yaml"))
|
||||
|
||||
full_url = pkg["repository"] + "?#" + pkg["commit"]
|
||||
with open(cofn, "r") as mmdfile:
|
||||
yaml = mmdfile.read()
|
||||
finally:
|
||||
try:
|
||||
if td is not None:
|
||||
shutil.rmtree(td)
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
"Failed to remove temporary directory {!r}: {}".format(
|
||||
td, str(e)))
|
||||
|
||||
if not rida.scm.SCM(full_url).is_available():
|
||||
raise UnprocessableEntity("Cannot checkout %s" % pkgname)
|
||||
mmd = modulemd.ModuleMetadata()
|
||||
try:
|
||||
mmd.loads(yaml)
|
||||
except:
|
||||
raise UnprocessableEntity('Invalid modulemd')
|
||||
|
||||
build = models.ComponentBuild(
|
||||
module_id=module.id,
|
||||
package=pkgname,
|
||||
format="rpms",
|
||||
scmurl=full_url,
|
||||
if models.ModuleBuild.query.filter_by(name=mmd.name,
|
||||
version=mmd.version,
|
||||
release=mmd.release).first():
|
||||
raise Conflict('Module already exists')
|
||||
|
||||
module = models.ModuleBuild.create(
|
||||
db.session,
|
||||
conf,
|
||||
name=mmd.name,
|
||||
version=mmd.version,
|
||||
release=mmd.release,
|
||||
modulemd=yaml,
|
||||
scmurl=url,
|
||||
username=username
|
||||
)
|
||||
db.session.add(build)
|
||||
|
||||
module.modulemd = mmd.dumps()
|
||||
module.transition(conf, models.BUILD_STATES["wait"])
|
||||
db.session.add(module)
|
||||
db.session.commit()
|
||||
logging.info("%s submitted build of %s-%s-%s", username, mmd.name,
|
||||
mmd.version, mmd.release)
|
||||
return jsonify(module.json()), 201
|
||||
for pkgname, pkg in mmd.components.rpms.packages.items():
|
||||
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"]
|
||||
|
||||
@app.route("/rida/module-builds/", methods=["GET"])
|
||||
def query_builds():
|
||||
if not rida.scm.SCM(full_url).is_available():
|
||||
raise UnprocessableEntity("Cannot checkout %s" % pkgname)
|
||||
|
||||
build = models.ComponentBuild(
|
||||
module_id=module.id,
|
||||
package=pkgname,
|
||||
format="rpms",
|
||||
scmurl=full_url,
|
||||
)
|
||||
db.session.add(build)
|
||||
|
||||
module.modulemd = mmd.dumps()
|
||||
module.transition(conf, models.BUILD_STATES["wait"])
|
||||
db.session.add(module)
|
||||
db.session.commit()
|
||||
logging.info("%s submitted build of %s-%s-%s", username, mmd.name,
|
||||
mmd.version, mmd.release)
|
||||
return jsonify(module.json()), 201
|
||||
|
||||
class QueryBuilds(View):
|
||||
"""Lists all tracked module builds."""
|
||||
p_query = filter_module_builds(request)
|
||||
|
||||
json_data = {
|
||||
'meta': pagination_metadata(p_query)
|
||||
}
|
||||
def dispatch_builds_request(self):
|
||||
"""Lists all tracked module builds."""
|
||||
p_query = filter_module_builds(request)
|
||||
|
||||
verbose_flag = request.args.get('verbose', 'false')
|
||||
json_data = {
|
||||
'meta': pagination_metadata(p_query)
|
||||
}
|
||||
|
||||
if verbose_flag.lower() == 'true' or verbose_flag == '1':
|
||||
json_data['items'] = [item.api_json() for item in p_query.items]
|
||||
else:
|
||||
json_data['items'] = [{'id': item.id, 'state': item.state} for item in p_query.items]
|
||||
verbose_flag = request.args.get('verbose', 'false')
|
||||
|
||||
return jsonify(json_data), 200
|
||||
if verbose_flag.lower() == 'true' or verbose_flag == '1':
|
||||
json_data['items'] = [item.api_json() for item in p_query.items]
|
||||
else:
|
||||
json_data['items'] = [{'id': item.id, 'state': item.state} for item in p_query.items]
|
||||
|
||||
return jsonify(json_data), 200
|
||||
|
||||
@app.route("/rida/module-builds/<int:id>", methods=["GET"])
|
||||
def query_build(id):
|
||||
"""Lists details for the specified module builds."""
|
||||
module = models.ModuleBuild.query.filter_by(id=id).first()
|
||||
def dispatch_build_request(self, id):
|
||||
"""Lists details for the specified module builds."""
|
||||
|
||||
if module:
|
||||
return jsonify(module.api_json()), 200
|
||||
else:
|
||||
raise NotFound('No such module found.')
|
||||
module = models.ModuleBuild.query.filter_by(id=id).first()
|
||||
|
||||
if module:
|
||||
return jsonify(module.api_json()), 200
|
||||
else:
|
||||
raise NotFound('No such module found.')
|
||||
|
||||
def dispatch_request(self, id):
|
||||
if id is None:
|
||||
return self.dispatch_builds_request()
|
||||
else:
|
||||
return self.dispatch_build_request(id)
|
||||
|
||||
def register_v1_api():
|
||||
""" Registers version 1 of Rida API. """
|
||||
|
||||
query_builds = QueryBuilds.as_view("query-builds")
|
||||
module_builds = SubmitBuild.as_view("module-builds")
|
||||
|
||||
app.add_url_rule('/rida/1/module-builds/',
|
||||
view_func=module_builds,
|
||||
methods=['POST'])
|
||||
app.add_url_rule('/rida/1/module-builds/',
|
||||
defaults={'id': None}, view_func=query_builds,
|
||||
methods=['GET'])
|
||||
app.add_url_rule('/rida/1/module-builds/<int:id>',
|
||||
view_func=query_builds,
|
||||
methods=['GET'])
|
||||
|
||||
register_v1_api()
|
||||
|
||||
@@ -37,7 +37,7 @@ class TestViews(unittest.TestCase):
|
||||
init_data()
|
||||
|
||||
def test_query_build(self):
|
||||
rv = self.client.get('/rida/module-builds/1')
|
||||
rv = self.client.get('/rida/1/module-builds/1')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['id'], 1)
|
||||
self.assertEquals(data['name'], 'nginx')
|
||||
@@ -52,29 +52,29 @@ class TestViews(unittest.TestCase):
|
||||
self.assertEquals(data['time_submitted'], '2016-09-03T11:23:20Z')
|
||||
|
||||
def test_pagination_metadata(self):
|
||||
rv = self.client.get('/rida/module-builds/?per_page=8&page=2')
|
||||
rv = self.client.get('/rida/1/module-builds/?per_page=8&page=2')
|
||||
meta_data = json.loads(rv.data)['meta']
|
||||
self.assertTrue(
|
||||
'rida/module-builds/?per_page=8&page=1' in meta_data['prev'])
|
||||
'rida/1/module-builds/?per_page=8&page=1' in meta_data['prev'])
|
||||
self.assertTrue(
|
||||
'rida/module-builds/?per_page=8&page=3' in meta_data['next'])
|
||||
'rida/1/module-builds/?per_page=8&page=3' in meta_data['next'])
|
||||
self.assertTrue(
|
||||
'rida/module-builds/?per_page=8&page=4' in meta_data['last'])
|
||||
'rida/1/module-builds/?per_page=8&page=4' in meta_data['last'])
|
||||
self.assertTrue(
|
||||
'rida/module-builds/?per_page=8&page=1' in meta_data['first'])
|
||||
'rida/1/module-builds/?per_page=8&page=1' in meta_data['first'])
|
||||
self.assertEquals(meta_data['total'], 30)
|
||||
self.assertEquals(meta_data['per_page'], 8)
|
||||
self.assertEquals(meta_data['pages'], 4)
|
||||
self.assertEquals(meta_data['page'], 2)
|
||||
|
||||
def test_query_builds(self):
|
||||
rv = self.client.get('/rida/module-builds/?per_page=2')
|
||||
rv = self.client.get('/rida/1/module-builds/?per_page=2')
|
||||
items = json.loads(rv.data)['items']
|
||||
self.assertEquals(items,
|
||||
[{u'state': 3, u'id': 1}, {u'state': 3, u'id': 2}])
|
||||
|
||||
def test_query_builds_verbose(self):
|
||||
rv = self.client.get('/rida/module-builds/?per_page=2&verbose=True')
|
||||
rv = self.client.get('/rida/1/module-builds/?per_page=2&verbose=True')
|
||||
item = json.loads(rv.data)['items'][1]
|
||||
self.assertEquals(item['id'], 2)
|
||||
self.assertEquals(item['name'], 'postgressql')
|
||||
@@ -90,67 +90,67 @@ class TestViews(unittest.TestCase):
|
||||
self.assertEquals(item['time_submitted'], '2016-09-03T12:25:33Z')
|
||||
|
||||
def test_query_builds_filter_name(self):
|
||||
rv = self.client.get('/rida/module-builds/?name=nginx')
|
||||
rv = self.client.get('/rida/1/module-builds/?name=nginx')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 10)
|
||||
|
||||
def test_query_builds_filter_completed_before(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?completed_before=2016-09-03T11:30:00Z')
|
||||
'/rida/1/module-builds/?completed_before=2016-09-03T11:30:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 2)
|
||||
|
||||
def test_query_builds_filter_completed_after(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?completed_after=2016-09-03T12:25:00Z')
|
||||
'/rida/1/module-builds/?completed_after=2016-09-03T12:25:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 8)
|
||||
|
||||
def test_query_builds_filter_submitted_before(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?submitted_before=2016-09-03T12:25:00Z')
|
||||
'/rida/1/module-builds/?submitted_before=2016-09-03T12:25:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 7)
|
||||
|
||||
def test_query_builds_filter_submitted_after(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?submitted_after=2016-09-03T12:25:00Z')
|
||||
'/rida/1/module-builds/?submitted_after=2016-09-03T12:25:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 23)
|
||||
|
||||
def test_query_builds_filter_modified_before(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?modified_before=2016-09-03T12:25:00Z')
|
||||
'/rida/1/module-builds/?modified_before=2016-09-03T12:25:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 6)
|
||||
|
||||
def test_query_builds_filter_modified_after(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?modified_after=2016-09-03T12:25:00Z')
|
||||
'/rida/1/module-builds/?modified_after=2016-09-03T12:25:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 24)
|
||||
|
||||
def test_query_builds_filter_owner(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?owner=Moe%20Szyslak')
|
||||
'/rida/1/module-builds/?owner=Moe%20Szyslak')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 10)
|
||||
|
||||
def test_query_builds_filter_state(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?state=3')
|
||||
'/rida/1/module-builds/?state=3')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 20)
|
||||
|
||||
def test_query_builds_two_filters(self):
|
||||
rv = self.client.get('/rida/module-builds/?owner=Moe%20Szyslak'
|
||||
rv = self.client.get('/rida/1/module-builds/?owner=Moe%20Szyslak'
|
||||
'&modified_after=2016-09-03T12:25:00Z')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['meta']['total'], 4)
|
||||
|
||||
def test_query_builds_filter_invalid_date(self):
|
||||
rv = self.client.get(
|
||||
'/rida/module-builds/?modified_after=2016-09-03T12:25:00-05:00')
|
||||
'/rida/1/module-builds/?modified_after=2016-09-03T12:25:00-05:00')
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(data['error'], 'Bad Request')
|
||||
self.assertEquals(data['message'], 'An invalid Zulu ISO 8601 timestamp'
|
||||
@@ -173,7 +173,7 @@ class TestViews(unittest.TestCase):
|
||||
|
||||
mocked_scm.return_value.checkout = mocked_scm_checkout
|
||||
mocked_scm.return_value.name = 'fakemodule'
|
||||
rv = self.client.post('/rida/module-builds/', data=json.dumps(
|
||||
rv = self.client.post('/rida/1/module-builds/', data=json.dumps(
|
||||
{'scmurl': 'git://pkgs.stg.fedoraproject.org/modules/'
|
||||
'testmodule.git?#68932c90de214d9d13feefbd35246a81b6cb8d49'}))
|
||||
data = json.loads(rv.data)
|
||||
@@ -194,7 +194,7 @@ class TestViews(unittest.TestCase):
|
||||
self.assertEquals(data['state_name'], 'wait')
|
||||
|
||||
def test_submit_build_cert_error(self):
|
||||
rv = self.client.post('/rida/module-builds/', data=json.dumps(
|
||||
rv = self.client.post('/rida/1/module-builds/', data=json.dumps(
|
||||
{'scmurl': 'git://pkgs.stg.fedoraproject.org/modules/'
|
||||
'testmodule.git?#48932b90de214d9d13feefbd35246a81b6cb8d49'}))
|
||||
data = json.loads(rv.data)
|
||||
@@ -209,11 +209,11 @@ class TestViews(unittest.TestCase):
|
||||
@patch('rida.auth.assert_is_packager')
|
||||
def test_submit_build_scm_url_error(self, mocked_assert_is_packager,
|
||||
mocked_get_username):
|
||||
rv = self.client.post('/rida/module-builds/', data=json.dumps(
|
||||
rv = self.client.post('/rida/1/module-builds/', data=json.dumps(
|
||||
{'scmurl': 'git://badurl.com'}))
|
||||
data = json.loads(rv.data)
|
||||
self.assertEquals(
|
||||
data['message'], 'The submitted scmurl isn\'t allowed')
|
||||
data['message'], 'The submitted scmurl is not allowed')
|
||||
self.assertEquals(data['status'], 401)
|
||||
self.assertEquals(data['error'], 'Unauthorized')
|
||||
|
||||
@@ -223,7 +223,7 @@ class TestViews(unittest.TestCase):
|
||||
def test_submit_build_bad_modulemd(self, mocked_scm,
|
||||
mocked_assert_is_packager,
|
||||
mocked_get_username):
|
||||
rv = self.client.post('/rida/module-builds/', data=json.dumps(
|
||||
rv = self.client.post('/rida/1/module-builds/', data=json.dumps(
|
||||
{'scmurl': 'git://badurl.com'}))
|
||||
def mocked_scm_checkout(temp_dir):
|
||||
scm_dir = path.join(temp_dir, 'fakemodule')
|
||||
@@ -237,7 +237,7 @@ class TestViews(unittest.TestCase):
|
||||
|
||||
mocked_scm.return_value.checkout = mocked_scm_checkout
|
||||
mocked_scm.return_value.name = 'fakemodule'
|
||||
rv = self.client.post('/rida/module-builds/', data=json.dumps(
|
||||
rv = self.client.post('/rida/1/module-builds/', data=json.dumps(
|
||||
{'scmurl': 'git://pkgs.stg.fedoraproject.org/modules/'
|
||||
'testmodule.git?#68932c90de214d9d13feefbd35246a81b6cb8d49'}))
|
||||
data = json.loads(rv.data)
|
||||
|
||||
Reference in New Issue
Block a user