Add update_reviews tool

This tool lifts all of the user's reviews that they have -2d for
a project using the gerrit REST API.

Change-Id: Ie955417e1d3d068b07a445649dec61c14861f520
This commit is contained in:
Brant Knudson 2016-03-09 15:06:28 -06:00
parent 6d7348b07f
commit 3c369591e9
6 changed files with 252 additions and 0 deletions

View File

@ -784,3 +784,21 @@ Example::
Add a 'You won!' comment (with subject line 'Winner') to Launchpad
bugs #1000000 and #2000000
update_reviews
--------------
Lift your -2 reviews from a project. Use this after the stable branch has been
created and the project is ready for accept new features.
This tool uses the Gerrit REST API. So you need to provide your username and
password somehow. You probably already have a .gertty.yaml, if not make one.
Example::
update_reviews oslo.config
The tool looks for all of the changes in the project that you have a -2 vote on
and changes your vote to 0, with the message "This project is now open for new
features."

View File

@ -0,0 +1,31 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import warnings
from releasetools import update_reviews
def updating_review_cb(r):
print('Updating %s in %s' % (r['change_id'], r['project']))
def main():
# Get urllib3 to shut up.
warnings.simplefilter('ignore', Warning)
project = sys.argv[1]
u_r = update_reviews.UpdateReviews(project,
updating_review_cb=updating_review_cb)
u_r.update_my_reviews()

View File

@ -0,0 +1,119 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
from oslotest import base
from oslotest import mockpatch
import requests_mock
from releasetools import update_reviews
class TestUpdateReviews(base.BaseTestCase):
def _patch_conf(self):
fake_conf = {
'username': mock.sentinel.username,
'password': mock.sentinel.password,
'url': 'https://review.openstack.org/',
}
po = mockpatch.PatchObject(update_reviews, '_read_config',
return_value=fake_conf)
self.useFixture(po)
def test_update_my_reviews(self):
self._patch_conf()
u_r = update_reviews.UpdateReviews(mock.sentinel.project)
sample_reviews = [mock.sentinel.r1, mock.sentinel.r2]
po = mockpatch.PatchObject(u_r, '_list_my_reviews',
return_value=sample_reviews)
list_my_reviews_mock = self.useFixture(po).mock
update_review_mock = self.useFixture(
mockpatch.PatchObject(u_r, '_update_review')).mock
u_r.update_my_reviews()
self.assertEqual(1, list_my_reviews_mock.call_count)
exp_calls = [mock.call(mock.sentinel.r1), mock.call(mock.sentinel.r2)]
self.assertEqual(exp_calls, update_review_mock.call_args_list)
def test_updating_review_callback(self):
self._patch_conf()
cb = mock.Mock()
u_r = update_reviews.UpdateReviews(mock.sentinel.project,
updating_review_cb=cb)
sample_reviews = [mock.sentinel.r1, mock.sentinel.r2]
po = mockpatch.PatchObject(u_r, '_list_my_reviews',
return_value=sample_reviews)
self.useFixture(po).mock
self.useFixture(
mockpatch.PatchObject(u_r, '_update_review')).mock
u_r.update_my_reviews()
exp_calls = [mock.call(mock.sentinel.r1), mock.call(mock.sentinel.r2)]
self.assertEqual(exp_calls, cb.call_args_list)
@requests_mock.mock()
def test_list_my_reviews(self, m):
self._patch_conf()
u_r = update_reviews.UpdateReviews(mock.sentinel.project)
sample_result = []
gerrit_magic_prefix = ")]}'\n"
sample_text = '%s%s' % (gerrit_magic_prefix, json.dumps(sample_result))
m.get('https://review.openstack.org/a/changes/', text=sample_text)
ret = u_r._list_my_reviews()
self.assertEqual(sample_result, ret)
exp_qs = {
'q': ['project:%s branch:master status:open '
'label:code-review=-2,self' % mock.sentinel.project],
'o': ['current_revision'],
}
self.assertEqual(exp_qs, m.request_history[0].qs)
@requests_mock.mock()
def test_update_review(self, m):
self._patch_conf()
u_r = update_reviews.UpdateReviews(mock.sentinel.project)
change_id = mock.sentinel.change_id
revision_id = mock.sentinel.revision_id
url_base = 'https://review.openstack.org/'
url = ('%sa/changes/%s/revisions/%s/review' %
(url_base, change_id, revision_id))
m.post(url)
review = {
'id': change_id,
'current_revision': revision_id,
}
u_r._update_review(review)
exp_req = {
'message': 'This project is now open for new features.',
'labels': {
'Code-Review': 0,
},
}
self.assertEqual(exp_req, m.request_history[0].json())

View File

@ -0,0 +1,82 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import os.path
import pbr.version
import requests
from requests import auth
import yaml
__version__ = pbr.version.VersionInfo('update_reviews').version_string()
def _read_config():
data = yaml.safe_load(open(os.path.expanduser('~/.gertty.yaml')))
servers = data['servers']
for s in servers:
if s['name'] == 'openstack':
return s
class UpdateReviews(object):
def __init__(self, project, user=None, password=None,
updating_review_cb=lambda x: None):
self.base_url = 'https://review.openstack.org/'
if not (user and password):
config = _read_config()
user = config['username']
password = config['password']
self.base_url = config['url']
self.auth = auth.HTTPDigestAuth(user, password)
self.project = project
self.updating_review_cb = updating_review_cb
def _list_my_reviews(self):
url = '%sa/changes/' % self.base_url
branch = 'master'
query = ('project:%s branch:%s status:open '
'label:Code-Review=-2,self' % (self.project, branch))
params = {'q': query, 'o': 'CURRENT_REVISION'}
r = requests.get(url, params=params, auth=self.auth)
r.raise_for_status()
# Note that the result is not JSON, it's got a leading line that needs
# to be removed first.
res_text = r.text
(dummy_magic_prefix, dummy_nl, res_json) = res_text.partition('\n')
return json.loads(res_json)
def _update_review(self, r):
change_id = r['id']
revision_id = r['current_revision']
url = ('%sa/changes/%s/revisions/%s/review' %
(self.base_url, change_id, revision_id))
headers = {'Content-Type': 'application/json'}
payload = {
'message': 'This project is now open for new features.',
'labels': {
'Code-Review': 0,
},
}
r = requests.post(url, auth=self.auth, headers=headers, json=payload)
r.raise_for_status()
def update_my_reviews(self):
for r in self._list_my_reviews():
self.updating_review_cb(r)
self._update_review(r)

View File

@ -36,3 +36,4 @@ console_scripts =
send-mail = releasetools.cmds.mail:main
dashboard = releasetools.cmds.dashboard:main
batch-stable-branches = releasetools.cmds.batch_create_stable_branches:main
update-reviews = releasetools.cmds.update_reviews:main

View File

@ -10,3 +10,4 @@ testrepository>=0.0.18
testscenarios>=0.4
testtools>=1.4.0
oslotest>=1.10.0 # Apache-2.0
requests-mock>=0.7.0 # Apache-2.0