Commit 6fbf12f2 authored by Michael Achenbach's avatar Michael Achenbach Committed by Commit Bot

Enable query for changes in gerrit recipe module

This'll allow to query gerrit from recipes for e.g. current changes in
CQ.

Bug: 685318
Change-Id: I73d08d4b186b2e5fe044fd4d4fafd9db62e27066
Reviewed-on: https://chromium-review.googlesource.com/558939
Commit-Queue: Michael Achenbach <machenbach@chromium.org>
Reviewed-by: 's avatarAndrii Shyshkalov <tandrii@chromium.org>
Reviewed-by: 's avatarAaron Gable <agable@chromium.org>
parent c6846aa2
...@@ -63,6 +63,28 @@ def CMDbranch(parser, args): ...@@ -63,6 +63,28 @@ def CMDbranch(parser, args):
write_result(result, opt) write_result(result, opt)
@subcommand.usage('[args ...]')
def CMDchanges(parser, args):
parser.add_option('-p', '--param', dest='params', action='append',
help='repeatable query parameter, format: -p key=value')
parser.add_option('--limit', dest='limit', type=int,
help='maximum number of results to return')
parser.add_option('--start', dest='start', type=int,
help='how many changes to skip '
'(starting with the most recent)')
(opt, args) = parser.parse_args(args)
result = gerrit_util.QueryChanges(
urlparse.urlparse(opt.host).netloc,
list(tuple(p.split('=', 1)) for p in opt.params),
start=opt.start, # Default: None
limit=opt.limit, # Default: None
)
logging.info('Change query returned %d changes.', len(result))
write_result(result, opt)
class OptionParser(optparse.OptionParser): class OptionParser(optparse.OptionParser):
"""Creates the option parse and add --verbose support.""" """Creates the option parse and add --verbose support."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
...@@ -102,4 +124,4 @@ if __name__ == '__main__': ...@@ -102,4 +124,4 @@ if __name__ == '__main__':
sys.exit(main(sys.argv[1:])) sys.exit(main(sys.argv[1:]))
except KeyboardInterrupt: except KeyboardInterrupt:
sys.stderr.write('interrupted\n') sys.stderr.write('interrupted\n')
sys.exit(1) sys.exit(1)
\ No newline at end of file
...@@ -50,13 +50,13 @@ class GerritAuthenticationError(GerritError): ...@@ -50,13 +50,13 @@ class GerritAuthenticationError(GerritError):
"""Exception class for authentication errors during Gerrit communication.""" """Exception class for authentication errors during Gerrit communication."""
def _QueryString(param_dict, first_param=None): def _QueryString(params, first_param=None):
"""Encodes query parameters in the key:val[+key:val...] format specified here: """Encodes query parameters in the key:val[+key:val...] format specified here:
https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
""" """
q = [urllib.quote(first_param)] if first_param else [] q = [urllib.quote(first_param)] if first_param else []
q.extend(['%s:%s' % (key, val) for key, val in param_dict.iteritems()]) q.extend(['%s:%s' % (key, val) for key, val in params])
return '+'.join(q) return '+'.join(q)
...@@ -387,14 +387,15 @@ def ReadHttpJsonResponse(conn, accept_statuses=frozenset([200])): ...@@ -387,14 +387,15 @@ def ReadHttpJsonResponse(conn, accept_statuses=frozenset([200])):
return json.loads(s) return json.loads(s)
def QueryChanges(host, param_dict, first_param=None, limit=None, o_params=None, def QueryChanges(host, params, first_param=None, limit=None, o_params=None,
start=None): start=None):
""" """
Queries a gerrit-on-borg server for changes matching query terms. Queries a gerrit-on-borg server for changes matching query terms.
Args: Args:
param_dict: A dictionary of search parameters, as documented here: params: A list of key:value pairs for search parameters, as documented
http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/user-search.html here (e.g. ('is', 'owner') for a parameter 'is:owner'):
https://gerrit-review.googlesource.com/Documentation/user-search.html#search-operators
first_param: A change identifier first_param: A change identifier
limit: Maximum number of results to return. limit: Maximum number of results to return.
start: how many changes to skip (starting with the most recent) start: how many changes to skip (starting with the most recent)
...@@ -404,9 +405,9 @@ def QueryChanges(host, param_dict, first_param=None, limit=None, o_params=None, ...@@ -404,9 +405,9 @@ def QueryChanges(host, param_dict, first_param=None, limit=None, o_params=None,
A list of json-decoded query results. A list of json-decoded query results.
""" """
# Note that no attempt is made to escape special characters; YMMV. # Note that no attempt is made to escape special characters; YMMV.
if not param_dict and not first_param: if not params and not first_param:
raise RuntimeError('QueryChanges requires search parameters') raise RuntimeError('QueryChanges requires search parameters')
path = 'changes/?q=%s' % _QueryString(param_dict, first_param) path = 'changes/?q=%s' % _QueryString(params, first_param)
if start: if start:
path = '%s&start=%s' % (path, start) path = '%s&start=%s' % (path, start)
if limit: if limit:
...@@ -416,7 +417,7 @@ def QueryChanges(host, param_dict, first_param=None, limit=None, o_params=None, ...@@ -416,7 +417,7 @@ def QueryChanges(host, param_dict, first_param=None, limit=None, o_params=None,
return ReadHttpJsonResponse(CreateHttpConn(host, path)) return ReadHttpJsonResponse(CreateHttpConn(host, path))
def GenerateAllChanges(host, param_dict, first_param=None, limit=500, def GenerateAllChanges(host, params, first_param=None, limit=500,
o_params=None, start=None): o_params=None, start=None):
""" """
Queries a gerrit-on-borg server for all the changes matching the query terms. Queries a gerrit-on-borg server for all the changes matching the query terms.
...@@ -429,7 +430,7 @@ def GenerateAllChanges(host, param_dict, first_param=None, limit=500, ...@@ -429,7 +430,7 @@ def GenerateAllChanges(host, param_dict, first_param=None, limit=500,
limit. limit.
Args: Args:
param_dict, first_param: Refer to QueryChanges(). params, first_param: Refer to QueryChanges().
limit: Maximum number of requested changes per query. limit: Maximum number of requested changes per query.
o_params: Refer to QueryChanges(). o_params: Refer to QueryChanges().
start: Refer to QueryChanges(). start: Refer to QueryChanges().
...@@ -457,7 +458,7 @@ def GenerateAllChanges(host, param_dict, first_param=None, limit=500, ...@@ -457,7 +458,7 @@ def GenerateAllChanges(host, param_dict, first_param=None, limit=500,
# > E get's updated. New order: EABCDFGH # > E get's updated. New order: EABCDFGH
# query[3..6] => CDF # C is a dup # query[3..6] => CDF # C is a dup
# query[6..9] => GH # E is missed. # query[6..9] => GH # E is missed.
page = QueryChanges(host, param_dict, first_param, limit, o_params, page = QueryChanges(host, params, first_param, limit, o_params,
cur_start) cur_start)
for cl in at_most_once(page): for cl in at_most_once(page):
yield cl yield cl
...@@ -474,20 +475,20 @@ def GenerateAllChanges(host, param_dict, first_param=None, limit=500, ...@@ -474,20 +475,20 @@ def GenerateAllChanges(host, param_dict, first_param=None, limit=500,
# If we paged through, query again the first page which in most circumstances # If we paged through, query again the first page which in most circumstances
# will fetch all changes that were modified while this function was run. # will fetch all changes that were modified while this function was run.
if start != cur_start: if start != cur_start:
page = QueryChanges(host, param_dict, first_param, limit, o_params, start) page = QueryChanges(host, params, first_param, limit, o_params, start)
for cl in at_most_once(page): for cl in at_most_once(page):
yield cl yield cl
def MultiQueryChanges(host, param_dict, change_list, limit=None, o_params=None, def MultiQueryChanges(host, params, change_list, limit=None, o_params=None,
start=None): start=None):
"""Initiate a query composed of multiple sets of query parameters.""" """Initiate a query composed of multiple sets of query parameters."""
if not change_list: if not change_list:
raise RuntimeError( raise RuntimeError(
"MultiQueryChanges requires a list of change numbers/id's") "MultiQueryChanges requires a list of change numbers/id's")
q = ['q=%s' % '+OR+'.join([urllib.quote(str(x)) for x in change_list])] q = ['q=%s' % '+OR+'.join([urllib.quote(str(x)) for x in change_list])]
if param_dict: if params:
q.append(_QueryString(param_dict)) q.append(_QueryString(params))
if limit: if limit:
q.append('n=%d' % limit) q.append('n=%d' % limit)
if start: if start:
...@@ -540,12 +541,12 @@ def GetChangeCommit(host, change, revision='current'): ...@@ -540,12 +541,12 @@ def GetChangeCommit(host, change, revision='current'):
def GetChangeCurrentRevision(host, change): def GetChangeCurrentRevision(host, change):
"""Get information about the latest revision for a given change.""" """Get information about the latest revision for a given change."""
return QueryChanges(host, {}, change, o_params=('CURRENT_REVISION',)) return QueryChanges(host, [], change, o_params=('CURRENT_REVISION',))
def GetChangeRevisions(host, change): def GetChangeRevisions(host, change):
"""Get information about all revisions associated with a change.""" """Get information about all revisions associated with a change."""
return QueryChanges(host, {}, change, o_params=('ALL_REVISIONS',)) return QueryChanges(host, [], change, o_params=('ALL_REVISIONS',))
def GetChangeReview(host, change, revision=None): def GetChangeReview(host, change, revision=None):
......
...@@ -342,8 +342,8 @@ class MyActivity(object): ...@@ -342,8 +342,8 @@ class MyActivity(object):
@staticmethod @staticmethod
def gerrit_changes_over_rest(instance, filters): def gerrit_changes_over_rest(instance, filters):
# Convert the "key:value" filter to a dictionary. # Convert the "key:value" filter to a list of (key, value) pairs.
req = dict(f.split(':', 1) for f in filters) req = list(f.split(':', 1) for f in filters)
try: try:
# Instantiate the generator to force all the requests now and catch the # Instantiate the generator to force all the requests now and catch the
# errors here. # errors here.
......
...@@ -62,3 +62,37 @@ class GerritApi(recipe_api.RecipeApi): ...@@ -62,3 +62,37 @@ class GerritApi(recipe_api.RecipeApi):
step_result = self(step_name, args, **kwargs) step_result = self(step_name, args, **kwargs)
revision = step_result.json.output.get('revision') revision = step_result.json.output.get('revision')
return revision return revision
def get_changes(self, host, query_params, start=None, limit=None, **kwargs):
"""
Query changes for the given host.
Args:
host: Gerrit host to query.
query_params: Query parameters as list of (key, value) tuples to form a
query as documented here:
https://gerrit-review.googlesource.com/Documentation/user-search.html#search-operators
start: How many changes to skip (starting with the most recent).
limit: Maximum number of results to return.
Returns:
A list of change dicts as documented here:
https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
"""
args = [
'changes',
'--host', host,
'--json_file', self.m.json.output()
]
if start:
args += ['--start', str(start)]
if limit:
args += ['--limit', str(limit)]
for k, v in query_params:
args += ['-p', '%s=%s' % (k, v)]
return self(
'changes',
args,
step_test_data=lambda: self.test_api.get_changes_response_data(),
**kwargs
).json.output
...@@ -58,6 +58,48 @@ ...@@ -58,6 +58,48 @@
"@@@STEP_LOG_END@json.output@@@" "@@@STEP_LOG_END@json.output@@@"
] ]
}, },
{
"cmd": [
"python",
"-u",
"RECIPE_PACKAGE_REPO[depot_tools]/gerrit_client.py",
"changes",
"--host",
"https://chromium-review.googlesource.com/a",
"--json_file",
"/path/to/tmp/json",
"--start",
"1",
"--limit",
"1",
"-p",
"project=chromium/src",
"-p",
"status=open",
"-p",
"label=Commit-Queue>0"
],
"env": {
"PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
},
"infra_step": true,
"name": "gerrit changes",
"~followup_annotations": [
"@@@STEP_LOG_LINE@json.output@[@@@",
"@@@STEP_LOG_LINE@json.output@ {@@@",
"@@@STEP_LOG_LINE@json.output@ \"_number\": \"91827\", @@@",
"@@@STEP_LOG_LINE@json.output@ \"branch\": \"master\", @@@",
"@@@STEP_LOG_LINE@json.output@ \"change_id\": \"Ideadbeef\", @@@",
"@@@STEP_LOG_LINE@json.output@ \"created\": \"2017-01-30 13:11:20.000000000\", @@@",
"@@@STEP_LOG_LINE@json.output@ \"has_review_started\": false, @@@",
"@@@STEP_LOG_LINE@json.output@ \"project\": \"chromium/src\", @@@",
"@@@STEP_LOG_LINE@json.output@ \"status\": \"NEW\", @@@",
"@@@STEP_LOG_LINE@json.output@ \"subject\": \"Change title\"@@@",
"@@@STEP_LOG_LINE@json.output@ }@@@",
"@@@STEP_LOG_LINE@json.output@]@@@",
"@@@STEP_LOG_END@json.output@@@"
]
},
{ {
"name": "$result", "name": "$result",
"recipe_result": null, "recipe_result": null,
......
...@@ -20,6 +20,17 @@ def RunSteps(api): ...@@ -20,6 +20,17 @@ def RunSteps(api):
data = api.gerrit.get_gerrit_branch(host, project, 'master') data = api.gerrit.get_gerrit_branch(host, project, 'master')
assert data == '67ebf73496383c6777035e374d2d664009e2aa5c' assert data == '67ebf73496383c6777035e374d2d664009e2aa5c'
# Query for changes in Chromium's CQ.
api.gerrit.get_changes(
host,
query_params=[
('project', 'chromium/src'),
('status', 'open'),
('label', 'Commit-Queue>0'),
],
start=1,
limit=1,
)
def GenTests(api): def GenTests(api):
yield ( yield (
......
...@@ -21,4 +21,20 @@ class GerritTestApi(recipe_test_api.RecipeTestApi): ...@@ -21,4 +21,20 @@ class GerritTestApi(recipe_test_api.RecipeTestApi):
return self._make_gerrit_response_json({ return self._make_gerrit_response_json({
"ref": "refs/heads/master", "ref": "refs/heads/master",
"revision": "67ebf73496383c6777035e374d2d664009e2aa5c" "revision": "67ebf73496383c6777035e374d2d664009e2aa5c"
}) })
\ No newline at end of file
def get_changes_response_data(self):
# Exemplary list of changes. Note: This contains only a subset of the
# key/value pairs present in production to limit recipe simulation output.
return self._make_gerrit_response_json([
{
'status': 'NEW',
'created': '2017-01-30 13:11:20.000000000',
'_number': '91827',
'change_id': 'Ideadbeef',
'project': 'chromium/src',
'has_review_started': False,
'branch': 'master',
'subject': 'Change title',
},
])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment