osclib/core: provide a series of request list generators.

Given the broken design of multi-action requests which continually wreaks
havoc on code attempting to handle them properly a series of methods for
searching for requests are provided to simplify the process. The core
principal is that both a request and action are returned since the
specific action that matched the search query is important.

Further poorly designed maintenance data structure is also abstracted to
provide a consistent interface for querying source changes regardless of
their state in the workflow.
This commit is contained in:
Jimmy Berry 2019-07-11 16:24:27 -05:00
parent 4e1aed28a1
commit b34a93e590

View File

@ -16,6 +16,7 @@ except ImportError:
from osc.core import get_binarylist
from osc.core import get_commitlog
from osc.core import get_dependson
from osc.core import get_request_list
from osc.core import http_GET
from osc.core import http_POST
from osc.core import http_PUT
@ -25,6 +26,7 @@ from osc.core import Request
from osc.core import show_package_meta
from osc.core import show_project_meta
from osc.core import show_results_meta
from osc.core import xpath_join
from osc.util.helper import decode_it
from osclib.conf import Config
from osclib.memoize import memoize
@ -804,3 +806,117 @@ def request_action_key(action):
identifier.append(action.src_package)
return '::'.join(['/'.join(identifier), action.type])
def request_action_list_maintenance_incident(apiurl, project, package, states=['new', 'review']):
# The maintenance workflow seems to be designed to be as difficult to find
# requests as possible. As such, in order to find incidents for a given
# target project one must search for the requests in two states: before and
# after being assigned to an incident project. Additionally, one must search
# the "maintenance projects" denoted by an attribute instead of the actual
# target project. To make matters worse the actual target project of the
# request is not accessible via search (ie. action/target/releaseproject)
# so it must be checked client side. Lastly, since multiple actions are also
# designed completely wrong one must loop over the actions and recheck the
# search parameters to figure out which action caused the request to be
# included in the search results. Overall, another prime example of design
# done completely and utterly wrong.
package_repository = '{}.{}'.format(package, project.replace(':', '_'))
# Loop over all maintenance projects and create selectors for the two
# request states for the given project.
xpath = ''
for maintenance_project in project_attribute_list(apiurl, 'OBS:MaintenanceProject'):
xpath_project = ''
# Before being assigned to an incident.
xpath_project = xpath_join(xpath_project, 'action/target/@project="{}"'.format(
maintenance_project))
xpath_project = xpath_join(xpath_project, 'action/source/@package="{}"'.format(package), op='and', inner=True)
xpath = xpath_join(xpath, xpath_project, op='or', nexpr_parentheses=True)
xpath_project = ''
# After being assigned to an incident.
xpath_project = xpath_join(xpath_project, 'starts-with(action/target/@project,"{}:")'.format(
maintenance_project))
xpath_project = xpath_join(xpath_project, 'action/target/@package="{}"'.format(
package_repository), op='and', inner=True)
xpath = xpath_join(xpath, xpath_project, op='or', nexpr_parentheses=True)
xpath = '({})'.format(xpath)
if not 'all' in states:
xpath_states = ''
for state in states:
xpath_states = xpath_join(xpath_states, 'state/@name="{}"'.format(state), inner=True)
xpath = xpath_join(xpath, xpath_states, op='and', nexpr_parentheses=True)
xpath = xpath_join(xpath, 'action/@type="maintenance_incident"', op='and')
root = search(apiurl, 'request', xpath)
for request_element in root.findall('request'):
request = Request()
request.read(request_element)
for action in request.actions:
if action.type == 'maintenance_incident' and action.tgt_releaseproject == project and (
(action.tgt_package is None and action.src_package == package) or
(action.tgt_package == package_repository)):
yield request, action
break
def request_action_list_maintenance_release(apiurl, project, package, states=['new', 'review']):
package_repository = '{}.{}'.format(package, project.replace(':', '_'))
xpath = 'action/target/@project="{}"'.format(project)
xpath = xpath_join(xpath, 'action/source/@package="{}"'.format(package_repository), op='and', inner=True)
xpath = '({})'.format(xpath)
if not 'all' in states:
xpath_states = ''
for state in states:
xpath_states = xpath_join(xpath_states, 'state/@name="{}"'.format(state), inner=True)
xpath = xpath_join(xpath, xpath_states, op='and', nexpr_parentheses=True)
xpath = xpath_join(xpath, 'action/@type="maintenance_release"', op='and')
root = search(apiurl, 'request', xpath)
for request_element in root.findall('request'):
request = Request()
request.read(request_element)
for action in request.actions:
if (action.type == 'maintenance_release' and
action.tgt_project == project and action.src_package == package_repository):
yield request, action
break
def request_action_single_list(apiurl, project, package, states, request_type):
# TODO To be consistent this should not include request source from project.
for request in get_request_list(apiurl, project, package, None, states, request_type):
if len(request.actions) > 1:
raise Exception('request {} has more than one action'.format(request.reqid))
yield request, request.actions[0]
def request_action_list(apiurl, project, package, states=['new', 'review'], types=['submit']):
for request_type in types:
if request_type == 'maintenance_incident':
yield from request_action_list_maintenance_incident(apiurl, project, package, states)
if request_type == 'maintenance_release':
yield from request_action_list_maintenance_release(apiurl, project, package, states)
else:
yield from request_action_single_list(apiurl, project, package, states, request_type)
def request_action_list_source(apiurl, project, package, states=['new', 'review'], include_release=False):
types = []
if attribute_value_load(apiurl, project, 'Maintained', 'OBS'):
types.append('maintenance_incident')
if include_release:
types.append('maintenance_release')
else:
types.append('submit')
yield from request_action_list(apiurl, project, package, states, types)