Changes in MBS restful API:

- Refactor MBS API code
- Unify module-/component_build API philosophy/design/approach
- Naming fixes
- _utc_datetime_to_iso moved from ModuleBuildAPI and is now a module-level function.
- Existing v1 API remains unchanged. ComponentBuildAPI now supports individual component build listing + verbose mode.
- documented in README
- various component_build API tests added
This commit is contained in:
Filip Valder
2017-09-04 15:04:11 +02:00
parent 6cb836c862
commit 3cb41aa5dc
8 changed files with 175 additions and 74 deletions

View File

@@ -79,6 +79,19 @@ BUILD_STATES = {
INVERSE_BUILD_STATES = {v: k for k, v in BUILD_STATES.items()}
def _utc_datetime_to_iso(datetime_object):
"""
Takes a UTC datetime object and returns an ISO formatted string
:param datetime_object: datetime.datetime
:return: string with datetime in ISO format
"""
if datetime_object:
# Converts the datetime to ISO 8601
return datetime_object.strftime("%Y-%m-%dT%H:%M:%SZ")
return None
@contextlib.contextmanager
def _dummy_context_mgr():
"""
@@ -290,8 +303,8 @@ class ModuleBuild(MBSBase):
return []
local_modules = [m for m in local_modules
if m.koji_tag
and m.koji_tag.startswith(conf.mock_resultsdir)]
if m.koji_tag and
m.koji_tag.startswith(conf.mock_resultsdir)]
return local_modules
@classmethod
@@ -335,59 +348,37 @@ class ModuleBuild(MBSBase):
def json(self):
return {
'id': self.id,
'name': self.name,
'stream': self.stream,
'version': self.version,
'state': self.state,
'state_name': INVERSE_BUILD_STATES[self.state],
'state_reason': self.state_reason,
'state_url': get_url_for('module_build', id=self.id),
'scmurl': self.scmurl,
'owner': self.owner,
'time_submitted': self._utc_datetime_to_iso(self.time_submitted),
'time_modified': self._utc_datetime_to_iso(self.time_modified),
'time_completed': self._utc_datetime_to_iso(self.time_completed),
"tasks": self.tasks(),
'name': self.name,
'scmurl': self.scmurl,
'time_submitted': _utc_datetime_to_iso(self.time_submitted),
'time_modified': _utc_datetime_to_iso(self.time_modified),
'time_completed': _utc_datetime_to_iso(self.time_completed),
'koji_tag': self.koji_tag,
'tasks': self.tasks(),
}
def extended_json(self):
json = self.json()
json.update({
'stream': self.stream,
'version': self.version,
'state_url': get_url_for('module_build', id=self.id),
# TODO, show their entire .json() ?
'component_builds': [build.id for build in self.component_builds],
'modulemd': self.modulemd,
'koji_tag': self.koji_tag,
'state_trace': [{'time': self._utc_datetime_to_iso(record.state_time),
'state_trace': [{'time': _utc_datetime_to_iso(record.state_time),
'state': record.state,
'state_name': INVERSE_BUILD_STATES[record.state],
'reason': record.state_reason}
for record
in self.state_trace(self.id)]
}
})
@staticmethod
def _utc_datetime_to_iso(datetime_object):
"""
Takes a UTC datetime object and returns an ISO formatted string
:param datetime_object: datetime.datetime
:return: string with datetime in ISO format
"""
if datetime_object:
# Converts the datetime to ISO 8601
return datetime_object.strftime("%Y-%m-%dT%H:%M:%SZ")
return None
def api_json(self):
return {
"id": self.id,
"state": self.state,
'state_name': INVERSE_BUILD_STATES[self.state],
'state_reason': self.state_reason,
"owner": self.owner,
"name": self.name,
"scmurl": self.scmurl,
"time_submitted": self._utc_datetime_to_iso(self.time_submitted),
"time_modified": self._utc_datetime_to_iso(self.time_modified),
"time_completed": self._utc_datetime_to_iso(self.time_completed),
"koji_tag": self.koji_tag,
"tasks": self.tasks()
}
return json
def tasks(self):
"""
@@ -435,7 +426,7 @@ class ModuleBuildTrace(MBSBase):
retval = {
'id': self.id,
'module_id': self.module_id,
'state_time': self._utc_datetime_to_iso(self.state_time),
'state_time': _utc_datetime_to_iso(self.state_time),
'state': self.state,
'state_reason': self.state_reason,
}
@@ -495,6 +486,10 @@ class ComponentBuild(MBSBase):
return session.query(cls).filter_by(
package=component_name, module_id=module_id).first()
def state_trace(self, component_id):
return ComponentBuildTrace.query.filter_by(
component_id=component_id).order_by(ComponentBuildTrace.state_time).all()
def json(self):
retval = {
'id': self.id,
@@ -516,6 +511,19 @@ class ComponentBuild(MBSBase):
return retval
def extended_json(self):
json = self.json()
json.update({
'state_trace': [{'time': _utc_datetime_to_iso(record.state_time),
'state': record.state,
'state_name': INVERSE_BUILD_STATES[record.state],
'reason': record.state_reason}
for record
in self.state_trace(self.id)]
})
return json
def __repr__(self):
return "<ComponentBuild %s, %r, state: %r, task_id: %r, batch: %r, state_reason: %s>" % (
self.package, self.module_id, self.state, self.task_id, self.batch, self.state_reason)
@@ -536,7 +544,7 @@ class ComponentBuildTrace(MBSBase):
retval = {
'id': self.id,
'component_id': self.component_id,
'state_time': self._utc_datetime_to_iso(self.state_time),
'state_time': _utc_datetime_to_iso(self.state_time),
'state': self.state,
'state_reason': self.state_reason,
'task_id': self.task_id,

View File

@@ -150,7 +150,7 @@ def wait(config, session, msg):
log.info("Found build=%r from message" % build)
log.info("%r", build.modulemd)
module_info = build.json()
module_info = build.extended_json()
if module_info['state'] != msg.module_build_state:
log.warn("Note that retrieved module state %r "
"doesn't match message module state %r" % (

View File

@@ -67,6 +67,12 @@ api_v1 = {
'methods': ['GET'],
}
},
'component_build': {
'url': '/module-build-service/1/component-builds/<int:id>',
'options': {
'methods': ['GET'],
}
},
}
@@ -84,23 +90,23 @@ class ComponentBuildAPI(MethodView):
}
if verbose_flag.lower() == 'true' or verbose_flag == '1':
json_data['items'] = [item.api_json() for item in p_query.items]
json_data['items'] = [item.extended_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
else:
# Lists details for the specified module builds
module = models.ComponentBuild.query.filter_by(id=id).first()
# Lists details for the specified component builds
component = models.ComponentBuild.query.filter_by(id=id).first()
if module:
if component:
if verbose_flag.lower() == 'true' or verbose_flag == '1':
return jsonify(module.json()), 200
return jsonify(component.extended_json()), 200
else:
return jsonify(module.api_json()), 200
return jsonify(component.json()), 200
else:
raise NotFound('No such module found.')
raise NotFound('No such component found.')
class ModuleBuildAPI(MethodView):
@@ -117,7 +123,7 @@ class ModuleBuildAPI(MethodView):
}
if verbose_flag.lower() == 'true' or verbose_flag == '1':
json_data['items'] = [item.api_json() for item in p_query.items]
json_data['items'] = [item.extended_json() for item in p_query.items]
else:
json_data['items'] = [{'id': item.id, 'state': item.state} for
item in p_query.items]
@@ -129,9 +135,9 @@ class ModuleBuildAPI(MethodView):
if module:
if verbose_flag.lower() == 'true' or verbose_flag == '1':
return jsonify(module.json()), 200
return jsonify(module.extended_json()), 200
else:
return jsonify(module.api_json()), 200
return jsonify(module.json()), 200
else:
raise NotFound('No such module found.')
@@ -150,7 +156,7 @@ class ModuleBuildAPI(MethodView):
handler.validate()
module = handler.post()
return jsonify(module.json()), 201
return jsonify(module.extended_json()), 201
def patch(self, id):
username, groups = module_build_service.auth.get_user(request)
@@ -194,7 +200,7 @@ class ModuleBuildAPI(MethodView):
db.session.add(module)
db.session.commit()
return jsonify(module.api_json()), 200
return jsonify(module.json()), 200
class BaseHandler(object):
@@ -286,15 +292,17 @@ def register_api_v1():
module_view = ModuleBuildAPI.as_view('module_builds')
component_view = ComponentBuildAPI.as_view('component_builds')
for key, val in api_v1.items():
if key != 'component_builds_list':
if key.startswith('component_build'):
app.add_url_rule(val['url'],
endpoint=key,
view_func=component_view,
**val['options'])
elif key.startswith('module_build'):
app.add_url_rule(val['url'],
endpoint=key,
view_func=module_view,
**val['options'])
else:
app.add_url_rule(val['url'],
endpoint=key,
view_func=component_view,
**val['options'])
raise NotImplementedError("Unhandled api key.")
register_api_v1()