def _get_mozreview(self, where): if not where: if 'MOZREVIEW_HOME' in os.environ: where = os.environ['MOZREVIEW_HOME'] else: # Check for the path used by start-local-mozreview default_path = os.path.abspath( os.path.join(HERE, '..', '..', '..', 'mozreview-test')) if os.path.isdir(default_path): where = default_path web_image = os.environ.get('DOCKER_BMOWEB_IMAGE') hgrb_image = os.environ.get('DOCKER_HGRB_IMAGE') ldap_image = os.environ.get('DOCKER_LDAP_IMAGE') pulse_image = os.environ.get('DOCKER_PULSE_IMAGE') rbweb_image = os.environ.get('DOCKER_RBWEB_IMAGE') autolanddb_image = os.environ.get('DOCKER_AUTOLANDDB_IMAGE') autoland_image = os.environ.get('DOCKER_AUTOLAND_IMAGE') hgweb_image = os.environ.get('DOCKER_HGWEB_IMAGE') treestatus_image = os.environ.get('DOCKER_TREESTATUS_IMAGE') from vcttesting.mozreview import MozReview return MozReview(where, web_image=web_image, hgrb_image=hgrb_image, ldap_image=ldap_image, pulse_image=pulse_image, rbweb_image=rbweb_image, autolanddb_image=autolanddb_image, autoland_image=autoland_image, hgweb_image=hgweb_image, treestatus_image=treestatus_image)
def __init__(self, context): if 'BUGZILLA_URL' in os.environ: self.base_url = os.environ['BUGZILLA_URL'] username = os.environ['BUGZILLA_USERNAME'] password = os.environ['BUGZILLA_PASSWORD'] self.b = Bugzilla(self.base_url, username=username, password=password) elif 'MOZREVIEW_HOME' in os.environ: mr = MozReview(os.environ['MOZREVIEW_HOME']) username = os.environ.get('BUGZILLA_USERNAME') password = os.environ.get('BUGZILLA_PASSWORD') self.b = mr.get_bugzilla(username=username, password=password) else: raise Exception('Do not know which Bugzilla instance to talk to. ' 'Set BUGZILLA_URL or MOZREVIEW_HOME environment variables.')
def setUpClass(cls): tmpdir = tempfile.mkdtemp() cls._tmpdir = tmpdir try: mr = MozReview(tmpdir) except DockerNotAvailable: raise unittest.SkipTest('Docker not available') cls.mr = mr # If this fails mid-operation, we could have some services running. # unittest doesn't call tearDownClass if setUpClass fails. So do it # ourselves. try: start_mozreview(mr) except Exception: mr.stop() shutil.rmtree(tmpdir) raise
def __init__(self, context): if 'BUGZILLA_URL' in os.environ: self.base_url = os.environ['BUGZILLA_URL'] username = os.environ['BUGZILLA_USERNAME'] password = os.environ['BUGZILLA_PASSWORD'] self.b = Bugzilla(self.base_url, username=username, password=password) elif 'MOZREVIEW_HOME' in os.environ: mr = MozReview(os.environ['MOZREVIEW_HOME']) username = os.environ.get('BUGZILLA_USERNAME') password = os.environ.get('BUGZILLA_PASSWORD') self.b = mr.get_bugzilla(username=username, password=password) else: raise Exception( 'Do not know which Bugzilla instance to talk to. ' 'Set BUGZILLA_URL or MOZREVIEW_HOME environment variables.')
def setUpClass(cls): if 'SKIP_DOCKER_TESTS' in os.environ: raise unittest.SkipTest('Skipping tests that require Docker') tmpdir = tempfile.mkdtemp() cls._tmpdir = tmpdir try: mr = MozReview(tmpdir) except DockerNotAvailable: raise unittest.SkipTest('Docker not available') cls.mr = mr # If this fails mid-operation, we could have some services running. # unittest doesn't call tearDownClass if setUpClass fails. So do it # ourselves. try: start_mozreview(mr) except Exception: mr.stop() shutil.rmtree(tmpdir) raise
def __init__(self, context): if 'BUGZILLA_URL' in os.environ: self.base_url = os.environ['BUGZILLA_URL'] username = os.environ['BUGZILLA_USERNAME'] password = os.environ['BUGZILLA_PASSWORD'] self.b = Bugzilla(self.base_url, username=username, password=password) elif 'MOZREVIEW_HOME' in os.environ: # Delay import to facilitate module use in limited virtualenvs. from vcttesting.mozreview import MozReview mr = MozReview(os.environ['MOZREVIEW_HOME']) username = os.environ.get('BUGZILLA_USERNAME') password = os.environ.get('BUGZILLA_PASSWORD') self.b = mr.get_bugzilla(username=username, password=password) else: raise Exception( 'Do not know which Bugzilla instance to talk to. ' 'Set BUGZILLA_URL or MOZREVIEW_HOME environment variables.')
def __init__(self, context): from vcttesting.mozreview import MozReview if 'MOZREVIEW_HOME' in os.environ: self.mr = MozReview(os.environ['MOZREVIEW_HOME']) else: self.mr = None
class ReviewBoardCommands(object): def __init__(self, context): from vcttesting.mozreview import MozReview if 'MOZREVIEW_HOME' in os.environ: self.mr = MozReview(os.environ['MOZREVIEW_HOME']) else: self.mr = None def _get_client(self, username=None, password=None): from rbtools.api.client import RBClient from rbtools.api.transport.sync import SyncTransport class NoCacheTransport(SyncTransport): """API transport with disabled caching.""" def enable_cache(self): pass # TODO consider moving this to __init__. if not self.mr: raise Exception('Could not find MozReview cluster instance') if username is None or password is None: username = os.environ.get('BUGZILLA_USERNAME') password = os.environ.get('BUGZILLA_PASSWORD') # RBClient is persisting login cookies from call to call # in $HOME/.rbtools-cookies. We want to be able to easily switch # between users, so we clear that cookie between calls to the # server and reauthenticate every time. try: os.remove(os.path.join(os.environ.get('HOME'), '.rbtools-cookies')) except Exception: pass return RBClient(self.mr.reviewboard_url, username=username, password=password, transport_cls=NoCacheTransport) def _get_root(self, username=None, password=None): return self._get_client(username=username, password=password).get_root() def _get_rb(self, path=None): from vcttesting.reviewboard import MozReviewBoard if self.mr: return self.mr.get_reviewboard() elif 'BUGZILLA_HOME' in os.environ and path: return MozReviewBoard(None, os.environ['BUGZILLA_URL'], pulse_host=os.environ.get('PULSE_HOST'), pulse_port=os.environ.get('PULSE_PORT')) elif 'REVIEWBOARD_URL' in os.environ: return MozReviewBoard(None, None, os.environ['REVIEWBOARD_URL']) else: raise Exception('Do not know about Bugzilla URL. Cannot talk to ' 'Review Board. Try running `mozreview start` and ' 'setting MOZREVIEW_HOME.') @Command('dumpreview', category='reviewboard', description='Print a representation of a review request.') @CommandArgument('rrid', help='Review request id to dump') def dumpreview(self, rrid): root = self._get_root() r = root.get_review_request(review_request_id=rrid) print(serialize_review_requests(r)) @Command('add-reviewer', category='reviewboard', description='Add a reviewer to a review request') @CommandArgument('rrid', help='Review request id to modify') @CommandArgument('--user', action='append', help='User from whom to ask for review') def add_reviewer(self, rrid, user): from rbtools.api.errors import APIError root = self._get_root() rr = root.get_review_request(review_request_id=rrid) people = [] draft = rr.get_or_create_draft() for p in draft.target_people: people.append(p.title) # Review Board doesn't call into the auth plugin when mapping target # people to RB users. So, we perform an API call here to ensure the # user is present. for u in user: if u not in people: people.append(u) root.get_users(q=u) people = ','.join(people) draft = rr.get_or_create_draft(target_people=people) print('%d people listed on review request' % len(draft.target_people)) @Command('list-reviewers', category='reviewboard', description='List reviewers on a review request') @CommandArgument('rrid', help='Review request id for which to list reviewers') @CommandArgument('--draft', action='store_true', help='List reviewers on the current draft') def list_reviewers(self, rrid, draft): from rbtools.api.errors import APIError root = self._get_root() rr = root.get_review_request(review_request_id=rrid) people = [] if draft: try: for p in rr.get_draft().target_people: people.append(p.title) except APIError: pass else: for p in rr.target_people: people.append(p.title) print(', '.join(sorted(people))) @Command('remove-reviewer', category='reviewboard', description='Remove a reviewer from a review request') @CommandArgument('rrid', help='Review request id to modify') @CommandArgument('--user', action='append', help='User to remove from review') def remove_reviewer(self, rrid, user): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) people = [] for p in rr.target_people: username = p.get().username if username not in user: people.append(username) people = ','.join(people) draft = rr.get_or_create_draft(target_people=people) print('%d people listed on review request' % len(draft.target_people)) @Command('publish', category='reviewboard', description='Publish a review request') @CommandArgument('rrid', help='Review request id to publish') def publish(self, rrid): from rbtools.api.errors import APIError root = self._get_root() r = root.get_review_request(review_request_id=rrid) try: response = r.get_draft().update(public=True) # TODO: Dump the response code? except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 @Command('get-users', category='reviewboard', description='Query the Review Board user list') @CommandArgument('q', help='Query string') def query_users(self, q=None): from rbtools.api.errors import APIError root = self._get_root() try: r = root.get_users(q=q, fullname=True) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 users = [] for u in r.rsp['users']: users.append(dict( id=u['id'], url=u['url'], username=u['username'])) print(yaml.safe_dump(users, default_flow_style=False).rstrip()) @Command('create-review', category='reviewboard', description='Create a new review on a review request') @CommandArgument('rrid', help='Review request to create the review on') @CommandArgument('--body-bottom', help='Review content below comments') @CommandArgument('--body-top', help='Review content above comments') @CommandArgument('--public', action='store_true', help='Whether to make this review public') @CommandArgument('--ship-it', action='store_true', help='Whether to mark the review "Ship It"') def create_review(self, rrid, body_bottom=None, body_top=None, public=False, ship_it=False): from rbtools.api.errors import APIError root = self._get_root() reviews = root.get_reviews(review_request_id=rrid) # rbtools will convert body_* to str() and insert "None" if we pass # an argument. args = {'public': public, 'ship_it': ship_it} if body_bottom: args['body_bottom'] = body_bottom if body_top: args['body_top'] = body_top try: r = reviews.create(**args) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 print('created review %s' % r.rsp['review']['id']) @Command('publish-review', category='reviewboard', description='Publish a review') @CommandArgument('rrid', help='Review request review is attached to') @CommandArgument('rid', help='Review to publish') def publish_review(self, rrid, rid): from rbtools.api.errors import APIError root = self._get_root() review = root.get_review(review_request_id=rrid, review_id=rid) try: review.update(public=True) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 print('published review %s' % review.id) @Command('create-review-reply', category='reviewboard', description='Create a reply to an existing review') @CommandArgument('rrid', help='Review request to create reply on') @CommandArgument('rid', help='Review to create reply on') @CommandArgument('--body-bottom', help='Reply content below the comments') @CommandArgument('--body-top', help='Reply content above the comments') @CommandArgument('--public', action='store_true', help='Whether to make this reply public') @CommandArgument('--text-type', default='plain', help='The format of the text') def create_review_reply(self, rrid, rid, body_bottom, body_top, public, text_type): root = self._get_root() replies = root.get_replies(review_request_id=rrid, review_id=rid) args = {'public': public, 'text_type': text_type} if body_bottom: args['body_bottom'] = body_bottom if body_top: args['body_top'] = body_top r = replies.create(**args) print('created review reply %s' % r.rsp['reply']['id']) @Command('create-diff-comment', category='reviewboard', description='Create a comment on a diff') @CommandArgument('rrid', help='Review request to create comment on') @CommandArgument('rid', help='Review to create comment on') @CommandArgument('filename', help='File to leave comment on') @CommandArgument('first_line', help='Line comment should apply to') @CommandArgument('text', help='Text constituting diff comment') @CommandArgument('--open-issue', action='store_true', help='Whether to open an issue in this review') def create_diff_comment(self, rrid, rid, filename, first_line, text, open_issue=False): root = self._get_root() diffs = root.get_diffs(review_request_id=rrid) diff = diffs[-1] files = diff.get_files() file_id = None for file_diff in files: if file_diff.source_file == filename: file_id = file_diff.id if not file_id: print('could not find file in diff: %s' % filename) return 1 reviews = root.get_reviews(review_request_id=rrid) review = reviews.create() comments = review.get_diff_comments() comment = comments.create(filediff_id=file_id, first_line=first_line, num_lines=1, text=text, issue_opened=open_issue) print('created diff comment %s' % comment.id) @Command('update-issue-status', category='reviewboard', description='Update issue status on a diff comment.') @CommandArgument('rrid', help='Review request for the diff comment review') @CommandArgument('rid', help='Review for the diff comment') @CommandArgument('cid', help='Diff comment of issue to be updated') @CommandArgument('status', help='Desired issue status ("open", "dropped", ' 'or "resolved")') def update_issue_status(self, rrid, rid, cid, status): root = self._get_root() review = root.get_review(review_request_id=rrid, review_id=rid) diff_comment = review.get_diff_comments().get_item(cid) diff_comment.update(issue_status=status) print('updated issue status on diff comment %s' % cid) @Command('closediscarded', category='reviewboard', description='Close a review request as discarded.') @CommandArgument('rrid', help='Request request to discard') def close_discarded(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) rr.update(status='discarded') @Command('closesubmitted', category='reviewboard', description='Close a review request as submitted.') @CommandArgument('rrid', help='Request request to submit') def close_submitted(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) rr.update(status='submitted') @Command('reopen', category='reviewboard', description='Reopen a closed review request') @CommandArgument('rrid', help='Review request to reopen') def reopen(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) rr.update(status='pending') @Command('discard-review-request-draft', category='reviewboard', description='Discard (delete) a draft review request.') @CommandArgument('rrid', help='Review request whose draft to delete') def discard_draft(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) draft = rr.get_draft() # Review Board sends an Content-Length 0 response with a JSON content # type. rbtools tries to parse this as JSON and raises a ValueError # in the process. This is a bug somewhere. Work around it by swallowing # the exception. try: draft.delete() except ValueError: pass print('Discarded draft for review request %s' % rrid) @Command('dump-user', category='reviewboard', description='Print a representation of a user.') @CommandArgument('username', help='Username whose info the print') def dump_user(self, username): root = self._get_root() u = root.get_user(username=username) o = {} for field in u.iterfields(): o[field] = getattr(u, field) data = {} data[u.id] = o print(yaml.safe_dump(data, default_flow_style=False).rstrip()) @Command('dump-user-ldap', category='reviewboard', description='Print the ldap username of a Review Board user.') @CommandArgument('username', help='Username whose info the print') def dump_user_ldap(self, username): root = self._get_root(username="******", password="******") ext = root.get_extension( extension_name='mozreview.extension.MozReviewExtension') user = ext.get_ldap_associations().get_item(username).ldap_username if user: print('ldap username: %s' % user) else: print('no ldap username associated with %s' % username) @Command('associate-ldap-user', category='reviewboard', description='Associate an LDAP email address with a user.') @CommandArgument('username', help='Username to associate with ldap') @CommandArgument('email', help='LDAP email to associate') def associate_ldap_user(self, username, email): # We use the "mozreview" account which has the special permission # to read / associate ldap email addresses. root = self._get_root(username="******", password="******") ext = root.get_extension( extension_name='mozreview.extension.MozReviewExtension') association = ext.get_ldap_associations().get_item(username) association.update(ldap_username=email) print('%s associated with %s' % (email, username)) @Command('dump-autoland-requests', category='reviewboard', description='Dump the table of autoland requests.') def dump_autoland_requests(self): root = self._get_root() ext = root.get_extension( extension_name="mozreview.extension.MozReviewExtension") requests = ext.get_try_autoland_triggers() o = {} for request in requests: for field in request.iterfields(): o[field] = getattr(request, field) print(yaml.safe_dump(o, default_flow_style=False).rstrip()) @Command('dump-summary', category='reviewboard', description='Return parent and child review-request summary.') @CommandArgument('rrid', help='Parent review request id') def dump_summary(self, rrid): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/summary/%s/' % rrid) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 d = OrderedDict() d['parent'] = short_review_request_dict(r['parent']) d['children'] = [short_review_request_dict(x) for x in r['children']] print(yaml.safe_dump(d, default_flow_style=False).rstrip()) @Command('dump-summaries-by-bug', category='reviewboard', description='Return parent and child review-request summaries ' 'for a given bug.') @CommandArgument('bug', help='Bug id') def dump_summaries_by_bug(self, bug): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/summary/', bug=bug) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 l = [] for summary in r: d = OrderedDict() d['parent'] = short_review_request_dict(summary['parent']) d['children'] = [short_review_request_dict(x) for x in summary['children']] l.append(d) print(yaml.safe_dump(l, default_flow_style=False).rstrip()) @Command('make-admin', category='reviewboard', description='Make a user a superuser and staff user') @CommandArgument('email', help='Email address of user to modify') def make_admin(self, email): self._get_rb().make_admin(email) @Command('dump-account-profile', category='reviewboard', description='Dump the contents of the auth_user table') @CommandArgument('username', help='Username whose info the print') def dump_account_profile(self, username): fields = self._get_rb().get_profile_data(username) for k, v in sorted(fields.items()): print('%s: %s' % (k, v)) @Command('convert-draft-rids-to-str', category='reviewboard', description='Convert any review ids stored in extra data to strings') @CommandArgument('rrid', help='Review request id convert') def convert_draft_rids_to_str(self, rrid): from rbtools.api.errors import APIError import json root = self._get_root() rr = root.get_review_request(review_request_id=rrid) try: draft = rr.get_draft() d = dict(draft.extra_data.iteritems()) extra_data = {} fields = ['p2rb.commits', 'p2rb.discard_on_publish_rids', 'p2rb.unpublished_rids'] for field in fields: if field not in d: continue try: value = [[x[0], str(x[1])] for x in json.loads(d[field])] except TypeError: value = [str(x) for x in json.loads(d[field])] extra_data['extra_data.' + field] = json.dumps(value) rr.get_or_create_draft(**extra_data) except APIError: pass @Command('add-repository', category='reviewboard', description='Add a repository to the server.') @CommandArgument('name', help='Name of repository') @CommandArgument('url', help='URL of repository') @CommandArgument('--bug-tracker', default='https://bugzilla.mozilla.org/', help='URL for bug tracker') def add_repository(self, name, url, bug_tracker=None): rb = self._get_rb() rid = rb.add_repository(name, url, bugzilla_url=bug_tracker, username=os.environ['BUGZILLA_USERNAME'], password=os.environ['BUGZILLA_PASSWORD']) print('Created repository %s' % rid)
class ReviewBoardCommands(object): def __init__(self, context): from vcttesting.mozreview import MozReview if 'MOZREVIEW_HOME' in os.environ: self.mr = MozReview(os.environ['MOZREVIEW_HOME']) else: self.mr = None def _get_client(self, username=None, password=None, anonymous=False): from rbtools.api.client import RBClient from rbtools.api.transport.sync import SyncTransport class NoCacheTransport(SyncTransport): """API transport with disabled caching.""" def enable_cache(self): pass # TODO consider moving this to __init__. if not self.mr: raise Exception('Could not find MozReview cluster instance') if username is None or password is None: username = os.environ.get('BUGZILLA_USERNAME') password = os.environ.get('BUGZILLA_PASSWORD') # RBClient is persisting login cookies from call to call # in $HOME/.rbtools-cookies. We want to be able to easily switch # between users, so we clear that cookie between calls to the # server and reauthenticate every time. try: os.remove(os.path.join(os.environ.get('HOME'), '.rbtools-cookies')) except Exception: pass if anonymous: return RBClient(self.mr.reviewboard_url, transport_cls=NoCacheTransport) return RBClient(self.mr.reviewboard_url, username=username, password=password, transport_cls=NoCacheTransport) def _get_root(self, username=None, password=None, anonymous=False): return self._get_client(username=username, password=password, anonymous=anonymous).get_root() def _get_rb(self, path=None): from vcttesting.reviewboard import MozReviewBoard if self.mr: return self.mr.get_reviewboard() elif 'BUGZILLA_HOME' in os.environ and path: return MozReviewBoard(None, os.environ['BUGZILLA_URL'], pulse_host=os.environ.get('PULSE_HOST'), pulse_port=os.environ.get('PULSE_PORT')) elif 'REVIEWBOARD_URL' in os.environ: return MozReviewBoard(None, None, os.environ['REVIEWBOARD_URL']) else: raise Exception('Do not know about Bugzilla URL. Cannot talk to ' 'Review Board. Try running `mozreview start` and ' 'setting MOZREVIEW_HOME.') @Command('dumpreview', category='reviewboard', description='Print a representation of a review request.') @CommandArgument('rrid', help='Review request id to dump') def dumpreview(self, rrid): client = self._get_client() root = client.get_root() r = root.get_review_request(review_request_id=rrid) print(serialize_review_requests(client, r)) @Command('dump-raw-diff', category='reviewboard', description='Dump the raw content of a diff from the server') @CommandArgument('rrid', help='Review request id of diffs to dump') def dump_raw_diff(self, rrid): from rbtools.api.errors import APIError client = self._get_client() root = client.get_root() rr = root.get_review_request(review_request_id=rrid) # mach wraps sys.stdout with a transparent UTF-8 writer. This # will interfere with our printing of raw data. So bypass it. stdout = os.fdopen(sys.stdout.fileno(), 'w') for diff in rr.get_diffs(): stdout.write(b'ID: %d\n' % diff.id) stdout.write(diff.get_patch().data) stdout.write(b'\n') try: draft = rr.get_draft() for diff in draft.get_draft_diffs(): stdout.write(b'ID: %d (draft)\n' % diff.id) stdout.write(diff.get_patch().data) stdout.write(b'\n') except APIError: pass stdout.close() @Command('add-reviewer', category='reviewboard', description='Add a reviewer to a review request') @CommandArgument('rrid', help='Review request id to modify') @CommandArgument('--user', action='append', help='User from whom to ask for review') def add_reviewer(self, rrid, user): from rbtools.api.errors import APIError root = self._get_root() rr = root.get_review_request(review_request_id=rrid) people = [] draft = rr.get_or_create_draft() for p in draft.target_people: people.append(p.title) # Review Board doesn't call into the auth plugin when mapping target # people to RB users. So, we perform an API call here to ensure the # user is present. for u in user: if u not in people: people.append(u) root.get_users(q=u) people = ','.join(people) draft = rr.get_or_create_draft(target_people=people) print('%d people listed on review request' % len(draft.target_people)) @Command('list-reviewers', category='reviewboard', description='List reviewers on a review request') @CommandArgument('rrid', help='Review request id for which to list reviewers') @CommandArgument('--draft', action='store_true', help='List reviewers on the current draft') def list_reviewers(self, rrid, draft): from rbtools.api.errors import APIError root = self._get_root() rr = root.get_review_request(review_request_id=rrid) people = [] if draft: try: for p in rr.get_draft().target_people: people.append(p.title) except APIError: pass else: for p in rr.target_people: people.append(p.title) print(', '.join(sorted(people))) @Command('remove-reviewer', category='reviewboard', description='Remove a reviewer from a review request') @CommandArgument('rrid', help='Review request id to modify') @CommandArgument('--user', action='append', help='User to remove from review') def remove_reviewer(self, rrid, user): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) people = [] for p in rr.target_people: username = p.get().username if username not in user: people.append(username) people = ','.join(people) draft = rr.get_or_create_draft(target_people=people) print('%d people listed on review request' % len(draft.target_people)) @Command('publish', category='reviewboard', description='Publish a review request') @CommandArgument('rrid', help='Review request id to publish') def publish(self, rrid): from rbtools.api.errors import APIError root = self._get_root() r = root.get_review_request(review_request_id=rrid) try: response = r.get_draft().update(public=True) # TODO: Dump the response code? except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 @Command( 'upload-diff', category='reviewboard', description='Upload a diff, read from stdin, to a review request') @CommandArgument( 'rrid', help='Review request id to upload diff to') @CommandArgument( '--base-commit', help='Base commit id to apply diff to') def upload_diff(self, rrid, base_commit=None): from rbtools.api.errors import APIError root = self._get_root() diffs = root.get_diffs(only_fields='', review_request_id=rrid) try: diffs.upload_diff( sys.stdin.read(), base_commit_id=base_commit) except APIError as e: print('API Error: %s: %s: %s' % ( e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 @Command('get-users', category='reviewboard', description='Query the Review Board user list') @CommandArgument('q', nargs='?', help='Query string') def query_users(self, q=None): from rbtools.api.errors import APIError root = self._get_root() try: args = {} # q=None will pass literal 'None' to the HTTP query! if q: args['q'] = q r = root.get_users(fullname=True, **args) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 users = [] for u in r.rsp['users']: users.append(dict( id=u['id'], url=u['url'], username=u['username'])) print(yaml.safe_dump(users, default_flow_style=False).rstrip()) @Command('create-review', category='reviewboard', description='Create a new review on a review request') @CommandArgument('rrid', help='Review request to create the review on') @CommandArgument('--body-bottom', help='Review content below comments') @CommandArgument('--body-top', help='Review content above comments') @CommandArgument('--public', action='store_true', help='Whether to make this review public') @CommandArgument('--review-flag', action='store', help='Bugzilla-style review flag to set') def create_review(self, rrid, body_bottom=None, body_top=None, public=False, review_flag=None): from rbtools.api.errors import APIError root = self._get_root() reviews = root.get_reviews(review_request_id=rrid) # rbtools will convert body_* to str() and insert "None" if we pass # an argument. args = {'public': public} if review_flag: args['ship_it'] = (review_flag == 'r+') args['extra_data.p2rb.review_flag'] = review_flag if body_bottom: args['body_bottom'] = body_bottom if body_top: args['body_top'] = body_top try: r = reviews.create(**args) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 print('created review %s' % r.rsp['review']['id']) @Command('publish-review', category='reviewboard', description='Publish a review') @CommandArgument('rrid', help='Review request review is attached to') @CommandArgument('rid', help='Review to publish') def publish_review(self, rrid, rid): from rbtools.api.errors import APIError root = self._get_root() review = root.get_review(review_request_id=rrid, review_id=rid) try: review.update(public=True) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 print('published review %s' % review.id) @Command('create-review-reply', category='reviewboard', description='Create a reply to an existing review') @CommandArgument('rrid', help='Review request to create reply on') @CommandArgument('rid', help='Review to create reply on') @CommandArgument('--body-bottom', help='Reply content below the comments') @CommandArgument('--body-top', help='Reply content above the comments') @CommandArgument('--public', action='store_true', help='Whether to make this reply public') @CommandArgument('--text-type', default='plain', help='The format of the text') def create_review_reply(self, rrid, rid, body_bottom, body_top, public, text_type): root = self._get_root() replies = root.get_replies(review_request_id=rrid, review_id=rid) args = {'public': public, 'text_type': text_type} if body_bottom: args['body_bottom'] = body_bottom if body_top: args['body_top'] = body_top r = replies.create(**args) print('created review reply %s' % r.rsp['reply']['id']) @Command('create-diff-comment', category='reviewboard', description='Create a comment on a diff') @CommandArgument('rrid', help='Review request to create comment on') @CommandArgument('rid', help='Review to create comment on') @CommandArgument('filename', help='File to leave comment on') @CommandArgument('first_line', help='Line comment should apply to') @CommandArgument('text', help='Text constituting diff comment') @CommandArgument('--open-issue', action='store_true', help='Whether to open an issue in this review') def create_diff_comment(self, rrid, rid, filename, first_line, text, open_issue=False): root = self._get_root() diffs = root.get_diffs(review_request_id=rrid) diff = diffs[-1] files = diff.get_files() file_id = None for file_diff in files: if file_diff.source_file == filename: file_id = file_diff.id if not file_id: print('could not find file in diff: %s' % filename) return 1 reviews = root.get_reviews(review_request_id=rrid) review = reviews.create() comments = review.get_diff_comments() comment = comments.create(filediff_id=file_id, first_line=first_line, num_lines=1, text=text, issue_opened=open_issue) print('created diff comment %s' % comment.id) @Command('update-issue-status', category='reviewboard', description='Update issue status on a diff comment.') @CommandArgument('rrid', help='Review request for the diff comment review') @CommandArgument('rid', help='Review for the diff comment') @CommandArgument('cid', help='Diff comment of issue to be updated') @CommandArgument('status', help='Desired issue status ("open", "dropped", ' 'or "resolved")') def update_issue_status(self, rrid, rid, cid, status): root = self._get_root() review = root.get_review(review_request_id=rrid, review_id=rid) diff_comment = review.get_diff_comments().get_item(cid) diff_comment.update(issue_status=status) print('updated issue status on diff comment %s' % cid) @Command('closediscarded', category='reviewboard', description='Close a review request as discarded.') @CommandArgument('rrid', help='Request request to discard') def close_discarded(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) rr.update(status='discarded') @Command('closesubmitted', category='reviewboard', description='Close a review request as submitted.') @CommandArgument('rrid', help='Request request to submit') def close_submitted(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) rr.update(status='submitted') @Command('reopen', category='reviewboard', description='Reopen a closed review request') @CommandArgument('rrid', help='Review request to reopen') def reopen(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) rr.update(status='pending') @Command('discard-review-request-draft', category='reviewboard', description='Discard (delete) a draft review request.') @CommandArgument('rrid', help='Review request whose draft to delete') def discard_draft(self, rrid): root = self._get_root() rr = root.get_review_request(review_request_id=rrid) draft = rr.get_draft() # Review Board sends an Content-Length 0 response with a JSON content # type. rbtools tries to parse this as JSON and raises a ValueError # in the process. This is a bug somewhere. Work around it by swallowing # the exception. try: draft.delete() except ValueError: pass print('Discarded draft for review request %s' % rrid) @Command('dump-user', category='reviewboard', description='Print a representation of a user.') @CommandArgument('username', help='Username whose info the print') def dump_user(self, username): root = self._get_root() u = root.get_user(username=username) o = {} for field in u.iterfields(): o[field] = getattr(u, field) data = {} data[u.id] = o print(yaml.safe_dump(data, default_flow_style=False).rstrip()) @Command('dump-user-ldap', category='reviewboard', description='Print the ldap username of a Review Board user.') @CommandArgument('username', help='Username whose info the print') def dump_user_ldap(self, username): root = self._get_root(username="******", password="******") ext = root.get_extension( extension_name='mozreview.extension.MozReviewExtension') user = ext.get_ldap_associations().get_item(username).ldap_username if user: print('ldap username: %s' % user) else: print('no ldap username associated with %s' % username) @Command('associate-ldap-user', category='reviewboard', description='Associate an LDAP email address with a user.') @CommandArgument('username', help='Username to associate with ldap') @CommandArgument('email', help='LDAP email to associate, or "none" to clear') @CommandArgument('--request-username', default='mozreview', help='Username to make request with') @CommandArgument('--request-password', default='mrpassword', help='Password to make request with') @CommandArgument('--anonymous', action='store_true', default=False, help='Make the request anonymously') def associate_ldap_user(self, username, email, request_username, request_password, anonymous): from rbtools.api.errors import APIError # We use the "mozreview" account by default which has the special # permission to read / associate ldap email addresses. root = self._get_root(username=request_username, password=request_password, anonymous=anonymous) ext = root.get_extension( extension_name='mozreview.extension.MozReviewExtension') if email == "none": email = None try: association = ext.get_ldap_associations().get_item(username) association.update(ldap_username=email) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 if email is None: print('association for %s cleared' % email) else: print('%s associated with %s' % (email, username)) @Command('associate-employee-ldap', category='reviewboard', description='Associate LDAP for Mozilla employees.') @CommandArgument('--request-username', default='mozreview', help='Username to make request with') @CommandArgument('--request-password', default='mrpassword', help='Password to make request with') @CommandArgument('--email', default='', help='LDAP email to associate') def associate_employee_ldap(self, email, request_username, request_password): from rbtools.api.errors import APIError # We use the "mozreview" account by default which has the special # permission to read / associate ldap email addresses. root = self._get_root(username=request_username, password=request_password) ext = root.get_extension( extension_name='mozreview.extension.MozReviewExtension') try: assoc = ext.get_employee_ldap_associations() r = assoc.create(email=email) if email: if r['errors']: print('LDAP association failed.') elif r['updated']: print('%s associated with %s' % (email, r['ldap_username'][0])) else: print('%s already associated with %s' % (email, r['ldap_username'][0])) else: print('updated: %s\nskipped: %s\nerrors: %s' % (r['updated'], r['skipped'], r['errors'])) except APIError as e: if e.rsp: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) else: print('API Error: %s' % e.http_status) @Command('dump-autoland-requests', category='reviewboard', description='Dump the table of autoland requests.') def dump_autoland_requests(self): root = self._get_root() ext = root.get_extension( extension_name="mozreview.extension.MozReviewExtension") requests = ext.get_try_autoland_triggers() o = {} for request in requests: for field in request.iterfields(): o[field] = getattr(request, field) print(yaml.safe_dump(o, default_flow_style=False).rstrip()) @Command('dump-summary', category='reviewboard', description='Return parent and child review-request summary.') @CommandArgument('rrid', help='Parent review request id') def dump_summary(self, rrid): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/summary/%s/' % rrid) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 d = OrderedDict() d['parent'] = short_review_request_dict(r['parent']) d['children'] = [short_review_request_dict(x) for x in r['children']] print(yaml.safe_dump(d, default_flow_style=False).rstrip()) @Command('dump-summaries-by-bug', category='reviewboard', description='Return parent and child review-request summaries ' 'for a given bug.') @CommandArgument('bug', help='Bug id') def dump_summaries_by_bug(self, bug): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/summary/', bug=bug) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 l = [] for summary in r: d = OrderedDict() d['parent'] = short_review_request_dict(summary['parent']) d['children'] = [short_review_request_dict(x) for x in summary['children']] l.append(d) print(yaml.safe_dump(l, default_flow_style=False).rstrip()) @Command('make-admin', category='reviewboard', description='Make a user a superuser and staff user') @CommandArgument('email', help='Email address of user to modify') def make_admin(self, email): self._get_rb().make_admin(email) @Command('dump-account-profile', category='reviewboard', description='Dump the contents of the auth_user table') @CommandArgument('username', help='Username whose info the print') def dump_account_profile(self, username): fields = self._get_rb().get_profile_data(username) for k, v in sorted(fields.items()): print('%s: %s' % (k, v)) @Command('convert-draft-rids-to-str', category='reviewboard', description='Convert any review ids stored in extra data to strings') @CommandArgument('rrid', help='Review request id convert') def convert_draft_rids_to_str(self, rrid): from rbtools.api.errors import APIError import json root = self._get_root() rr = root.get_review_request(review_request_id=rrid) try: draft = rr.get_draft() d = dict(draft.extra_data.iteritems()) extra_data = {} fields = ['p2rb.commits', 'p2rb.discard_on_publish_rids', 'p2rb.unpublished_rids'] for field in fields: if field not in d: continue try: value = [[x[0], str(x[1])] for x in json.loads(d[field])] except TypeError: value = [str(x) for x in json.loads(d[field])] extra_data['extra_data.' + field] = json.dumps(value) rr.get_or_create_draft(**extra_data) except APIError: pass @Command('add-repository', category='reviewboard', description='Add a repository to the server.') @CommandArgument('name', help='Name of repository') @CommandArgument('url', help='URL of repository') @CommandArgument('--bug-tracker', default='https://bugzilla.mozilla.org/', help='URL for bug tracker') def add_repository(self, name, url, bug_tracker=None): rb = self._get_rb() rid = rb.add_repository(name, url, bugzilla_url=bug_tracker, username=os.environ['BUGZILLA_USERNAME'], password=os.environ['BUGZILLA_PASSWORD']) print('Created repository %s' % rid) @Command('dump-rewrite-commit', category='reviewboard', description='Return the rewritten commit summaries') @CommandArgument('rrid', help='Parent review request id') def dump_rewrite_commit(self, rrid): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/commit_rewrite/%s/' % rrid) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 result = OrderedDict() result['commits'] = [] for commit in r: d = {} d['summary'] = _serialize_text(commit['summary']) d['id'] = commit['id'] d['commit'] = commit['commit'] for k in ('id', 'commit'): if k in commit: d[k] = commit[k] d['reviewers'] = list(commit['reviewers']) result['commits'].append(d) print(yaml.safe_dump(result, default_flow_style=False).rstrip()) @Command('modify-reviewers', category='reviewboard', description='Update reviewers on a child review request') @CommandArgument('parent_rrid', help='Parent review request id') @CommandArgument('child_rrid', help='Child review request id') @CommandArgument('reviewers', help='Comma delimited list of reviewers') def modify_reviewers(self, parent_rrid, child_rrid, reviewers): from rbtools.api.errors import APIError import json c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/modify-reviewers/') reviewers = {child_rrid: reviewers.split(',')} r.create(parent_request_id=parent_rrid, reviewers=json.dumps(reviewers)) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 @Command('verify-reviewers', category='reviewboard', description='Update reviewers on a child review request') @CommandArgument('reviewers', help='Comma delimited list of reviewers') def verify_reviewers(self, reviewers): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/verify-reviewers/') r.create(reviewers=reviewers) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1 @Command('ensure-drafts', category='reviewboard', description='Create drafts on all review request children') @CommandArgument('parent_rrid', help='Parent review request id') def ensure_drafts(self, parent_rrid): from rbtools.api.errors import APIError c = self._get_client() try: r = c.get_path('/extensions/mozreview.extension.MozReviewExtension' '/ensure-drafts/') r.create(parent_request_id=parent_rrid) except APIError as e: print('API Error: %s: %s: %s' % (e.http_status, e.error_code, e.rsp['err']['msg'])) return 1