diff options
author | David Pursehouse <david.pursehouse@sonymobile.com> | 2014-03-14 12:20:31 +0900 |
---|---|---|
committer | David Pursehouse <david.pursehouse@sonymobile.com> | 2014-04-28 16:26:50 +0900 |
commit | f0b77968389966cd7bad0cac1fe7f04526eafde1 (patch) | |
tree | 8be265591681d9a1cc470ff77525dcfaa4ccdb68 | |
parent | c499b43b9cbf1f98364b75a1ea718b6b756b9c93 (diff) | |
download | pygerrit-f0b77968389966cd7bad0cac1fe7f04526eafde1.tar.gz |
Remove support for Gerrit over ssh
From now only the REST API is supported.
This makes installation of the package a bit lighter as we no
longer depend on paramiko and pycrypto
Change-Id: Ieda773d596fd43047c05895f3304690ddf709094
27 files changed, 62 insertions, 1599 deletions
@@ -8,7 +8,7 @@ Pygerrit - Client library for interacting with Gerrit Code Review :target: https://crate.io/packages/pygerrit/ Pygerrit is a Python library to interact with the -`Gerrit Code Review`_ system over ssh or via the REST API. +`Gerrit Code Review`_ system's REST API. Installation ------------ @@ -28,16 +28,6 @@ and 2.7.x. Support for other platforms and Python versions is not guaranteed. Configuration ------------- -For easier connection to the review server over ssh, the ssh connection -parameters (hostname, port, username) can be given in the user's ``.ssh/config`` -file:: - - Host review - HostName review.example.net - Port 29418 - User username - - For easier connection to the review server over the REST API, the user's HTTP username and password can be given in the user's ``.netrc`` file:: @@ -48,51 +38,6 @@ For instructions on how to obtain the HTTP password, refer to Gerrit's `HTTP upload settings`_ documentation. -SSH Interface -------------- - -The SSH interface can be used to run commands on the Gerrit server:: - - >>> from pygerrit.ssh import GerritSSHClient - >>> client = GerritSSHClient("review") - >>> result = client.run_gerrit_command("version") - >>> result - <GerritSSHCommandResult [version]> - >>> result.stdout - <paramiko.ChannelFile from <paramiko.Channel 2 (closed) -> <paramiko.Transport at 0xd2387d90L (cipher aes128-cbc, 128 bits) (active; 0 open channel(s))>>> - >>> result.stdout.read() - 'gerrit version 2.6.1\n' - >>> - -Event Stream ------------- - -Gerrit offers a ``stream-events`` command that is run over ssh, and returns back -a stream of events (new change uploaded, change merged, comment added, etc) as -JSON text. - -This library handles the parsing of the JSON text from the event stream, -encapsulating the data in event objects (Python classes), and allowing the -client to fetch them from a queue. It also allows users to easily add handling -of custom event types, for example if they are running a customised Gerrit -installation with non-standard events:: - - >>> from pygerrit.client import GerritClient - >>> client = GerritClient("review") - >>> client.gerrit_version() - '2.6.1' - >>> client.start_event_stream() - >>> client.get_event() - <CommentAddedEvent>: <Change 12345, platform/packages/apps/Example, master> <Patchset 1, 5c4b2f76297f04fbab77eb8c3462e087bc4b6f90> <Account Bob Example (bob.example@example.com)> - >>> client.get_event() - <CommentAddedEvent>: <Change 67890, platform/frameworks/example, master> <Patchset 2, c7d4f9956c80b1df66a66d66dea3960e71de4910> <Account John Example (john.example@example.com)> - >>> client.stop_event_stream() - >>> - - -Refer to the `example`_ script for a more detailed example of how the SSH -event stream interface works. - REST API -------- @@ -106,7 +51,7 @@ interface for clients to interact with Gerrit via the REST API:: >>> changes = rest.get("/changes/?q=owner:self%20status:open") -Refer to the `rest_example`_ script for a more detailed example of how the +Refer to the `example`_ script for a more detailed example of how the REST API interface works. Copyright and License @@ -121,6 +66,5 @@ license details. .. _`Gerrit Code Review`: https://code.google.com/p/gerrit/ .. _example: https://github.com/sonyxperiadev/pygerrit/blob/master/example.py -.. _rest_example: https://github.com/sonyxperiadev/pygerrit/blob/master/rest_example.py .. _`HTTP upload settings`: https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/user-upload.html#http .. _LICENSE: https://github.com/sonyxperiadev/pygerrit/blob/master/LICENSE @@ -3,7 +3,7 @@ # The MIT License # -# Copyright 2012 Sony Mobile Communications. All rights reserved. +# Copyright 2013 Sony Mobile Communications. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -23,89 +23,88 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -""" Example of using the Gerrit client class. """ +""" Example of using the Gerrit client REST API. """ import argparse import logging import sys -from threading import Event -import time -from pygerrit.client import GerritClient -from pygerrit.error import GerritError -from pygerrit.events import ErrorEvent +from requests.auth import HTTPBasicAuth, HTTPDigestAuth +from requests.exceptions import RequestException +try: + # pylint: disable=F0401 + from requests_kerberos import HTTPKerberosAuth, OPTIONAL + # pylint: enable=F0401 + _kerberos_support = True +except ImportError: + _kerberos_support = False + +from pygerrit.rest import GerritRestAPI +from pygerrit.auth import HTTPDigestAuthFromNetrc, HTTPBasicAuthFromNetrc def _main(): - descr = 'Send request using Gerrit ssh API' + descr = 'Send request using Gerrit HTTP API' parser = argparse.ArgumentParser( description=descr, formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('-g', '--gerrit-hostname', dest='hostname', - default='review', - help='gerrit server hostname') - parser.add_argument('-p', '--port', dest='port', - type=int, default=29418, - help='port number') + parser.add_argument('-g', '--gerrit-url', dest='gerrit_url', + required=True, + help='gerrit server url') + parser.add_argument('-b', '--basic-auth', dest='basic_auth', + action='store_true', + help='use basic auth instead of digest') + if _kerberos_support: + parser.add_argument('-k', '--kerberos-auth', dest='kerberos_auth', + action='store_true', + help='use kerberos auth') parser.add_argument('-u', '--username', dest='username', help='username') - parser.add_argument('-b', '--blocking', dest='blocking', + parser.add_argument('-p', '--password', dest='password', + help='password') + parser.add_argument('-n', '--netrc', dest='netrc', action='store_true', - help='block on event get') - parser.add_argument('-t', '--timeout', dest='timeout', - default=None, type=int, - metavar='SECONDS', - help='timeout for blocking event get') + help='Use credentials from netrc') parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='enable verbose (debug) logging') - parser.add_argument('-i', '--ignore-stream-errors', dest='ignore', - action='store_true', - help='do not exit when an error event is received') options = parser.parse_args() - if options.timeout and not options.blocking: - parser.error('Can only use --timeout with --blocking') level = logging.DEBUG if options.verbose else logging.INFO logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=level) - try: - gerrit = GerritClient(host=options.hostname, - username=options.username, - port=options.port) - logging.info("Connected to Gerrit version [%s]", - gerrit.gerrit_version()) - gerrit.start_event_stream() - except GerritError as err: - logging.error("Gerrit error: %s", err) - return 1 + if _kerberos_support and options.kerberos_auth: + if options.username or options.password \ + or options.basic_auth or options.netrc: + parser.error("--kerberos-auth may not be used together with " + "--username, --password, --basic-auth or --netrc") + auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) + elif options.username and options.password: + if options.netrc: + logging.warning("--netrc option ignored") + if options.basic_auth: + auth = HTTPBasicAuth(options.username, options.password) + else: + auth = HTTPDigestAuth(options.username, options.password) + elif options.netrc: + if options.basic_auth: + auth = HTTPBasicAuthFromNetrc(url=options.gerrit_url) + else: + auth = HTTPDigestAuthFromNetrc(url=options.gerrit_url) + else: + auth = None - errors = Event() - try: - while True: - event = gerrit.get_event(block=options.blocking, - timeout=options.timeout) - if event: - logging.info("Event: %s", event) - if isinstance(event, ErrorEvent) and not options.ignore: - logging.error(event.error) - errors.set() - break - else: - logging.info("No event") - if not options.blocking: - time.sleep(1) - except KeyboardInterrupt: - logging.info("Terminated by user") - finally: - logging.debug("Stopping event stream...") - gerrit.stop_event_stream() + rest = GerritRestAPI(url=options.gerrit_url, auth=auth) - if errors.isSet(): - logging.error("Exited with error") - return 1 + try: + changes = rest.get("/changes/?q=owner:self%20status:open") + logging.info("%d changes", len(changes)) + for change in changes: + logging.info(change['change_id']) + except RequestException as err: + logging.error("Error: %s", str(err)) if __name__ == "__main__": sys.exit(_main()) diff --git a/pygerrit/rest/auth.py b/pygerrit/auth.py index c43c3fa..c43c3fa 100644 --- a/pygerrit/rest/auth.py +++ b/pygerrit/auth.py diff --git a/pygerrit/client.py b/pygerrit/client.py deleted file mode 100644 index 4095914..0000000 --- a/pygerrit/client.py +++ /dev/null @@ -1,136 +0,0 @@ -# The MIT License -# -# Copyright 2012 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Gerrit client interface. """ - -from json import JSONDecoder -from Queue import Queue, Empty, Full - -from . import escape_string -from .error import GerritError -from .events import GerritEventFactory -from .models import Change -from .ssh import GerritSSHClient -from .stream import GerritStream - - -class GerritClient(object): - - """ Gerrit client interface. """ - - def __init__(self, host, username=None, port=None): - self._factory = GerritEventFactory() - self._events = Queue() - self._stream = None - self._ssh_client = GerritSSHClient(host, username=username, port=port) - - def gerrit_version(self): - """ Return the version of Gerrit that is connected to. """ - return self._ssh_client.get_remote_version() - - def gerrit_info(self): - """ Return the username, and version of Gerrit that is connected to. """ - return self._ssh_client.get_remote_info() - - def run_command(self, command): - """ Run the command. Return the result. """ - if not isinstance(command, basestring): - raise ValueError("command must be a string") - return self._ssh_client.run_gerrit_command(command) - - def query(self, term): - """ Run `gerrit query` with the given `term`. - - Return a list of results as `Change` objects. - - Raise `ValueError` if `term` is not a string. - - """ - results = [] - command = ["query", "--current-patch-set", "--all-approvals", - "--format JSON", "--commit-message"] - - if not isinstance(term, basestring): - raise ValueError("term must be a string") - - command.append(escape_string(term)) - result = self._ssh_client.run_gerrit_command(" ".join(command)) - decoder = JSONDecoder() - for line in result.stdout.read().splitlines(): - # Gerrit's response to the query command contains one or more - # lines of JSON-encoded strings. The last one is a status - # dictionary containing the key "type" whose value indicates - # whether or not the operation was successful. - # According to http://goo.gl/h13HD it should be safe to use the - # presence of the "type" key to determine whether the dictionary - # represents a change or if it's the query status indicator. - try: - data = decoder.decode(line) - except ValueError as err: - raise GerritError("Query returned invalid data: %s", err) - if "type" in data and data["type"] == "error": - raise GerritError("Query error: %s" % data["message"]) - elif "project" in data: - results.append(Change(data)) - return results - - def start_event_stream(self): - """ Start streaming events from `gerrit stream-events`. """ - if not self._stream: - self._stream = GerritStream(self, ssh_client=self._ssh_client) - self._stream.start() - - def stop_event_stream(self): - """ Stop streaming events from `gerrit stream-events`.""" - if self._stream: - self._stream.stop() - self._stream.join() - self._stream = None - with self._events.mutex: - self._events.queue.clear() - - def get_event(self, block=True, timeout=None): - """ Get the next event from the queue. - - Return a `GerritEvent` instance, or None if: - - `block` is False and there is no event available in the queue, or - - `block` is True and no event is available within the time - specified by `timeout`. - - """ - try: - return self._events.get(block, timeout) - except Empty: - return None - - def put_event(self, data): - """ Create event from `data` and add it to the queue. - - Raise GerritError if the queue is full, or the factory could not - create the event. - - """ - try: - event = self._factory.create(data) - self._events.put(event) - except Full: - raise GerritError("Unable to add event: queue is full") diff --git a/pygerrit/error.py b/pygerrit/error.py deleted file mode 100644 index b500812..0000000 --- a/pygerrit/error.py +++ /dev/null @@ -1,31 +0,0 @@ -# The MIT License -# -# Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved. -# Copyright 2012 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Error classes. """ - - -class GerritError(Exception): - - """ Raised when something goes wrong in Gerrit handling. """ - - pass diff --git a/pygerrit/events.py b/pygerrit/events.py deleted file mode 100644 index c6563cd..0000000 --- a/pygerrit/events.py +++ /dev/null @@ -1,331 +0,0 @@ -# The MIT License -# -# Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved. -# Copyright 2012 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Gerrit event classes. """ - -import json -import logging - -from .error import GerritError -from .models import Account, Approval, Change, Patchset, RefUpdate - - -class GerritEventFactory(object): - - """ Gerrit event factory. """ - - _events = {} - - @classmethod - def register(cls, name): - """ Decorator to register the event identified by `name`. - - Return the decorated class. - - Raise GerritError if the event is already registered. - - """ - - def decorate(klazz): - """ Decorator. """ - if name in cls._events: - raise GerritError("Duplicate event: %s" % name) - cls._events[name] = [klazz.__module__, klazz.__name__] - klazz.name = name - return klazz - return decorate - - @classmethod - def create(cls, data): - """ Create a new event instance. - - Return an instance of the `GerritEvent` subclass after converting - `data` to json. - - Raise GerritError if json parsed from `data` does not contain a `type` - key. - - """ - try: - json_data = json.loads(data) - except ValueError as err: - logging.debug("Failed to load json data: %s: [%s]", str(err), data) - json_data = json.loads(ErrorEvent.error_json(err)) - - if not "type" in json_data: - raise GerritError("`type` not in json_data") - name = json_data["type"] - if not name in cls._events: - name = 'unhandled-event' - event = cls._events[name] - module_name = event[0] - class_name = event[1] - module = __import__(module_name, fromlist=[module_name]) - klazz = getattr(module, class_name) - return klazz(json_data) - - -class GerritEvent(object): - - """ Gerrit event base class. """ - - def __init__(self, json_data): - self.json = json_data - - -@GerritEventFactory.register("unhandled-event") -class UnhandledEvent(GerritEvent): - - """ Unknown event type received in json data from Gerrit's event stream. """ - - def __init__(self, json_data): - super(UnhandledEvent, self).__init__(json_data) - - def __repr__(self): - return u"<UnhandledEvent>" - - -@GerritEventFactory.register("error-event") -class ErrorEvent(GerritEvent): - - """ Error occurred when processing json data from Gerrit's event stream. """ - - def __init__(self, json_data): - super(ErrorEvent, self).__init__(json_data) - self.error = json_data["error"] - - @classmethod - def error_json(cls, error): - """ Return a json string for the `error`. """ - return '{"type":"error-event",' \ - '"error":"%s"}' % str(error) - - def __repr__(self): - return u"<ErrorEvent: %s>" % self.error - - -@GerritEventFactory.register("patchset-created") -class PatchsetCreatedEvent(GerritEvent): - - """ Gerrit "patchset-created" event. """ - - def __init__(self, json_data): - super(PatchsetCreatedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.patchset = Patchset(json_data["patchSet"]) - self.uploader = Account(json_data["uploader"]) - except KeyError as e: - raise GerritError("PatchsetCreatedEvent: %s" % e) - - def __repr__(self): - return u"<PatchsetCreatedEvent>: %s %s %s" % (self.change, - self.patchset, - self.uploader) - - -@GerritEventFactory.register("draft-published") -class DraftPublishedEvent(GerritEvent): - - """ Gerrit "draft-published" event. """ - - def __init__(self, json_data): - super(DraftPublishedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.patchset = Patchset(json_data["patchSet"]) - self.uploader = Account(json_data["uploader"]) - except KeyError as e: - raise GerritError("DraftPublishedEvent: %s" % e) - - def __repr__(self): - return u"<DraftPublishedEvent>: %s %s %s" % (self.change, - self.patchset, - self.uploader) - - -@GerritEventFactory.register("comment-added") -class CommentAddedEvent(GerritEvent): - - """ Gerrit "comment-added" event. """ - - def __init__(self, json_data): - super(CommentAddedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.patchset = Patchset(json_data["patchSet"]) - self.author = Account(json_data["author"]) - self.approvals = [] - if "approvals" in json_data: - for approval in json_data["approvals"]: - self.approvals.append(Approval(approval)) - self.comment = json_data["comment"] - except (KeyError, ValueError) as e: - raise GerritError("CommentAddedEvent: %s" % e) - - def __repr__(self): - return u"<CommentAddedEvent>: %s %s %s" % (self.change, - self.patchset, - self.author) - - -@GerritEventFactory.register("change-merged") -class ChangeMergedEvent(GerritEvent): - - """ Gerrit "change-merged" event. """ - - def __init__(self, json_data): - super(ChangeMergedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.patchset = Patchset(json_data["patchSet"]) - self.submitter = Account(json_data["submitter"]) - except KeyError as e: - raise GerritError("ChangeMergedEvent: %s" % e) - - def __repr__(self): - return u"<ChangeMergedEvent>: %s %s %s" % (self.change, - self.patchset, - self.submitter) - - -@GerritEventFactory.register("merge-failed") -class MergeFailedEvent(GerritEvent): - - """ Gerrit "merge-failed" event. """ - - def __init__(self, json_data): - super(MergeFailedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.patchset = Patchset(json_data["patchSet"]) - self.submitter = Account(json_data["submitter"]) - if 'reason' in json_data: - self.reason = json_data["reason"] - except KeyError as e: - raise GerritError("MergeFailedEvent: %s" % e) - - def __repr__(self): - return u"<MergeFailedEvent>: %s %s %s" % (self.change, - self.patchset, - self.submitter) - - -@GerritEventFactory.register("change-abandoned") -class ChangeAbandonedEvent(GerritEvent): - - """ Gerrit "change-abandoned" event. """ - - def __init__(self, json_data): - super(ChangeAbandonedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.abandoner = Account(json_data["abandoner"]) - if 'reason' in json_data: - self.reason = json_data["reason"] - except KeyError as e: - raise GerritError("ChangeAbandonedEvent: %s" % e) - - def __repr__(self): - return u"<ChangeAbandonedEvent>: %s %s" % (self.change, - self.abandoner) - - -@GerritEventFactory.register("change-restored") -class ChangeRestoredEvent(GerritEvent): - - """ Gerrit "change-restored" event. """ - - def __init__(self, json_data): - super(ChangeRestoredEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.restorer = Account(json_data["restorer"]) - if 'reason' in json_data: - self.reason = json_data["reason"] - except KeyError as e: - raise GerritError("ChangeRestoredEvent: %s" % e) - - def __repr__(self): - return u"<ChangeRestoredEvent>: %s %s" % (self.change, - self.restorer) - - -@GerritEventFactory.register("ref-updated") -class RefUpdatedEvent(GerritEvent): - - """ Gerrit "ref-updated" event. """ - - def __init__(self, json_data): - super(RefUpdatedEvent, self).__init__(json_data) - try: - self.ref_update = RefUpdate(json_data["refUpdate"]) - self.submitter = Account.from_json(json_data, "submitter") - except KeyError as e: - raise GerritError("RefUpdatedEvent: %s" % e) - - def __repr__(self): - return u"<RefUpdatedEvent>: %s %s" % (self.ref_update, self.submitter) - - -@GerritEventFactory.register("reviewer-added") -class ReviewerAddedEvent(GerritEvent): - - """ Gerrit "reviewer-added" event. """ - - def __init__(self, json_data): - super(ReviewerAddedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.patchset = Patchset.from_json(json_data) - self.reviewer = Account(json_data["reviewer"]) - except KeyError as e: - raise GerritError("ReviewerAddedEvent: %s" % e) - - def __repr__(self): - return u"<ReviewerAddedEvent>: %s %s %s" % (self.change, - self.patchset, - self.reviewer) - - -@GerritEventFactory.register("topic-changed") -class TopicChangedEvent(GerritEvent): - - """ Gerrit "topic-changed" event. """ - - def __init__(self, json_data): - super(TopicChangedEvent, self).__init__(json_data) - try: - self.change = Change(json_data["change"]) - self.changer = Account(json_data["changer"]) - if "oldTopic" in json_data: - self.oldtopic = json_data["oldTopic"] - else: - self.oldtopic = "" - except KeyError as e: - raise GerritError("TopicChangedEvent: %s" % e) - - def __repr__(self): - return u"<TopicChangedEvent>: %s %s [%s]" % (self.change, - self.changer, - self.oldtopic) diff --git a/pygerrit/models.py b/pygerrit/models.py deleted file mode 100644 index 17dc7ee..0000000 --- a/pygerrit/models.py +++ /dev/null @@ -1,157 +0,0 @@ -# The MIT License -# -# Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved. -# Copyright 2012 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Models for Gerrit JSON data. """ - -from . import from_json - - -class Account(object): - - """ Gerrit user account (name and email address). """ - - def __init__(self, json_data): - self.name = from_json(json_data, "name") - self.email = from_json(json_data, "email") - self.username = from_json(json_data, "username") - - def __repr__(self): - return u"<Account %s%s>" % (self.name, - " (%s)" % self.email if self.email else "") - - @staticmethod - def from_json(json_data, key): - """ Create an Account instance. - - Return an instance of Account initialised with values from `key` - in `json_data`, or None if `json_data` does not contain `key`. - - """ - if key in json_data: - return Account(json_data[key]) - return None - - -class Change(object): - - """ Gerrit change. """ - - def __init__(self, json_data): - self.project = from_json(json_data, "project") - self.branch = from_json(json_data, "branch") - self.topic = from_json(json_data, "topic") - self.change_id = from_json(json_data, "id") - self.number = from_json(json_data, "number") - self.subject = from_json(json_data, "subject") - self.url = from_json(json_data, "url") - self.owner = Account.from_json(json_data, "owner") - self.sortkey = from_json(json_data, "sortKey") - self.status = from_json(json_data, "status") - self.current_patchset = CurrentPatchset.from_json(json_data) - - def __repr__(self): - return u"<Change %s, %s, %s>" % (self.number, self.project, self.branch) - - -class Patchset(object): - - """ Gerrit patch set. """ - - def __init__(self, json_data): - self.number = from_json(json_data, "number") - self.revision = from_json(json_data, "revision") - self.ref = from_json(json_data, "ref") - self.uploader = Account.from_json(json_data, "uploader") - - def __repr__(self): - return u"<Patchset %s, %s>" % (self.number, self.revision) - - @staticmethod - def from_json(json_data): - r""" Create a Patchset instance. - - Return an instance of Patchset initialised with values from "patchSet" - in `json_data`, or None if `json_data` does not contain "patchSet". - - """ - if "patchSet" in json_data: - return Patchset(json_data["patchSet"]) - return None - - -class CurrentPatchset(Patchset): - - """ Gerrit current patch set. """ - - def __init__(self, json_data): - super(CurrentPatchset, self).__init__(json_data) - self.author = Account.from_json(json_data, "author") - self.approvals = [] - if "approvals" in json_data: - for approval in json_data["approvals"]: - self.approvals.append(Approval(approval)) - - def __repr__(self): - return u"<CurrentPatchset %s, %s>" % (self.number, self.revision) - - @staticmethod - def from_json(json_data): - r""" Create a CurrentPatchset instance. - - Return an instance of CurrentPatchset initialised with values from - "currentPatchSet" in `json_data`, or None if `json_data` does not - contain "currentPatchSet". - - """ - if "currentPatchSet" in json_data: - return CurrentPatchset(json_data["currentPatchSet"]) - return None - - -class Approval(object): - - """ Gerrit approval (verified, code review, etc). """ - - def __init__(self, json_data): - self.category = from_json(json_data, "type") - self.value = from_json(json_data, "value") - self.description = from_json(json_data, "description") - self.approver = Account.from_json(json_data, "by") - - def __repr__(self): - return u"<Approval %s %s>" % (self.description, self.value) - - -class RefUpdate(object): - - """ Gerrit ref update. """ - - def __init__(self, json_data): - self.oldrev = from_json(json_data, "oldRev") - self.newrev = from_json(json_data, "newRev") - self.refname = from_json(json_data, "refName") - self.project = from_json(json_data, "project") - - def __repr__(self): - return "<RefUpdate %s %s %s %s>" % \ - (self.project, self.refname, self.oldrev, self.newrev) diff --git a/pygerrit/rest/__init__.py b/pygerrit/rest.py index cccfa94..cccfa94 100644 --- a/pygerrit/rest/__init__.py +++ b/pygerrit/rest.py diff --git a/pygerrit/ssh.py b/pygerrit/ssh.py deleted file mode 100644 index 2933ddf..0000000 --- a/pygerrit/ssh.py +++ /dev/null @@ -1,178 +0,0 @@ -# The MIT License -# -# Copyright 2012 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Gerrit SSH Client. """ - -from os.path import abspath, expanduser, isfile -import re -import socket -from threading import Event, Lock - -from .error import GerritError - -from paramiko import SSHClient, SSHConfig -from paramiko.ssh_exception import SSHException - - -def _extract_version(version_string, pattern): - """ Extract the version from `version_string` using `pattern`. - - Return the version as a string, with leading/trailing whitespace - stripped. - - """ - if version_string: - match = pattern.match(version_string.strip()) - if match: - return match.group(1) - return "" - - -class GerritSSHCommandResult(object): - - """ Represents the results of a Gerrit command run over SSH. """ - - def __init__(self, command, stdin, stdout, stderr): - self.command = command - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - - def __repr__(self): - return "<GerritSSHCommandResult [%s]>" % self.command - - -class GerritSSHClient(SSHClient): - - """ Gerrit SSH Client, wrapping the paramiko SSH Client. """ - - def __init__(self, hostname, username=None, port=None): - """ Initialise and connect to SSH. """ - super(GerritSSHClient, self).__init__() - self.remote_version = None - self.hostname = hostname - self.username = username - self.key_filename = None - self.port = port - self.connected = Event() - self.lock = Lock() - - def _configure(self): - """ Configure the ssh parameters from the config file. """ - configfile = expanduser("~/.ssh/config") - if not isfile(configfile): - raise GerritError("ssh config file '%s' does not exist" % - configfile) - - config = SSHConfig() - config.parse(open(configfile)) - data = config.lookup(self.hostname) - if not data: - raise GerritError("No ssh config for host %s" % self.hostname) - if not 'hostname' in data or not 'port' in data or not 'user' in data: - raise GerritError("Missing configuration data in %s" % configfile) - self.hostname = data['hostname'] - self.username = data['user'] - if 'identityfile' in data: - key_filename = abspath(expanduser(data['identityfile'][0])) - if not isfile(key_filename): - raise GerritError("Identity file '%s' does not exist" % - key_filename) - self.key_filename = key_filename - try: - self.port = int(data['port']) - except ValueError: - raise GerritError("Invalid port: %s" % data['port']) - - def _do_connect(self): - """ Connect to the remote. """ - self.load_system_host_keys() - if self.username is None or self.port is None: - self._configure() - try: - self.connect(hostname=self.hostname, - port=self.port, - username=self.username, - key_filename=self.key_filename) - except socket.error as e: - raise GerritError("Failed to connect to server: %s" % e) - - try: - version_string = self._transport.remote_version - pattern = re.compile(r'^.*GerritCodeReview_([a-z0-9-\.]*) .*$') - self.remote_version = _extract_version(version_string, pattern) - except AttributeError: - self.remote_version = None - - def _connect(self): - """ Connect to the remote if not already connected. """ - if not self.connected.is_set(): - try: - self.lock.acquire() - # Another thread may have connected while we were - # waiting to acquire the lock - if not self.connected.is_set(): - self._do_connect() - self.connected.set() - except GerritError: - raise - finally: - self.lock.release() - - def get_remote_version(self): - """ Return the version of the remote Gerrit server. """ - if self.remote_version is None: - result = self.run_gerrit_command("version") - version_string = result.stdout.read() - pattern = re.compile(r'^gerrit version (.*)$') - self.remote_version = _extract_version(version_string, pattern) - return self.remote_version - - def get_remote_info(self): - """ Return the username, and version of the remote Gerrit server. """ - version = self.get_remote_version() - return (self.username, version) - - def run_gerrit_command(self, command): - """ Run the given command. - - Make sure we're connected to the remote server, and run `command`. - - Return the results as a `GerritSSHCommandResult`. - - Raise `ValueError` if `command` is not a string, or `GerritError` if - command execution fails. - - """ - if not isinstance(command, basestring): - raise ValueError("command must be a string") - gerrit_command = "gerrit " + command - - self._connect() - try: - stdin, stdout, stderr = self.exec_command(gerrit_command, - bufsize=1, - timeout=None, - get_pty=False) - except SSHException as err: - raise GerritError("Command execution error: %s" % err) - return GerritSSHCommandResult(command, stdin, stdout, stderr) diff --git a/pygerrit/stream.py b/pygerrit/stream.py deleted file mode 100644 index 1504dde..0000000 --- a/pygerrit/stream.py +++ /dev/null @@ -1,77 +0,0 @@ -# The MIT License -# -# Copyright 2012 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Gerrit event stream interface. - -Class to listen to the Gerrit event stream and dispatch events. - -""" - -from threading import Thread, Event - -from .events import ErrorEvent - - -class GerritStream(Thread): - - """ Gerrit events stream handler. """ - - def __init__(self, gerrit, ssh_client): - Thread.__init__(self) - self.daemon = True - self._gerrit = gerrit - self._ssh_client = ssh_client - self._stop = Event() - self._channel = None - - def stop(self): - """ Stop the thread. """ - self._stop.set() - if self._channel is not None: - self._channel.close() - - def _error_event(self, error): - """ Dispatch `error` to the Gerrit client. """ - self._gerrit.put_event(ErrorEvent.error_json(error)) - - def run(self): - """ Listen to the stream and send events to the client. """ - channel = self._ssh_client.get_transport().open_session() - self._channel = channel - channel.exec_command("gerrit stream-events") - stdout = channel.makefile() - stderr = channel.makefile_stderr() - while not self._stop.is_set(): - try: - if channel.exit_status_ready(): - if channel.recv_stderr_ready(): - error = stderr.readline().strip() - else: - error = "Remote server connection closed" - self._error_event(error) - self._stop.set() - else: - data = stdout.readline() - self._gerrit.put_event(data) - except Exception as e: # pylint: disable=W0703 - self._error_event(repr(e)) - self._stop.set() diff --git a/requirements.txt b/requirements.txt index a3650ec..3c655d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1 @@ -paramiko==1.11.0 -pycrypto==2.3 requests==2.0.1 diff --git a/rest_example.py b/rest_example.py deleted file mode 100755 index 1b69c00..0000000 --- a/rest_example.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# The MIT License -# -# Copyright 2013 Sony Mobile Communications. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" Example of using the Gerrit client REST API. """ - -import argparse -import logging -import sys - -from requests.auth import HTTPBasicAuth, HTTPDigestAuth -from requests.exceptions import RequestException -try: - # pylint: disable=F0401 - from requests_kerberos import HTTPKerberosAuth, OPTIONAL - # pylint: enable=F0401 - _kerberos_support = True -except ImportError: - _kerberos_support = False - -from pygerrit.rest import GerritRestAPI -from pygerrit.rest.auth import HTTPDigestAuthFromNetrc, HTTPBasicAuthFromNetrc - - -def _main(): - descr = 'Send request using Gerrit HTTP API' - parser = argparse.ArgumentParser( - description=descr, - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('-g', '--gerrit-url', dest='gerrit_url', - required=True, - help='gerrit server url') - parser.add_argument('-b', '--basic-auth', dest='basic_auth', - action='store_true', - help='use basic auth instead of digest') - if _kerberos_support: - parser.add_argument('-k', '--kerberos-auth', dest='kerberos_auth', - action='store_true', - help='use kerberos auth') - parser.add_argument('-u', '--username', dest='username', - help='username') - parser.add_argument('-p', '--password', dest='password', - help='password') - parser.add_argument('-n', '--netrc', dest='netrc', - action='store_true', - help='Use credentials from netrc') - parser.add_argument('-v', '--verbose', dest='verbose', - action='store_true', - help='enable verbose (debug) logging') - - options = parser.parse_args() - - level = logging.DEBUG if options.verbose else logging.INFO - logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - level=level) - - if _kerberos_support and options.kerberos_auth: - if options.username or options.password \ - or options.basic_auth or options.netrc: - parser.error("--kerberos-auth may not be used together with " - "--username, --password, --basic-auth or --netrc") - auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) - elif options.username and options.password: - if options.netrc: - logging.warning("--netrc option ignored") - if options.basic_auth: - auth = HTTPBasicAuth(options.username, options.password) - else: - auth = HTTPDigestAuth(options.username, options.password) - elif options.netrc: - if options.basic_auth: - auth = HTTPBasicAuthFromNetrc(url=options.gerrit_url) - else: - auth = HTTPDigestAuthFromNetrc(url=options.gerrit_url) - else: - auth = None - - rest = GerritRestAPI(url=options.gerrit_url, auth=auth) - - try: - changes = rest.get("/changes/?q=owner:self%20status:open") - logging.info("%d changes", len(changes)) - for change in changes: - logging.info(change['change_id']) - except RequestException as err: - logging.error("Error: %s", str(err)) - -if __name__ == "__main__": - sys.exit(_main()) @@ -29,7 +29,7 @@ from setuptools import setup, find_packages from pygerrit import __version__ -REQUIRES = ['paramiko==1.11.0', 'pycrypto==2.3', 'requests==2.0.1'] +REQUIRES = ['requests==2.0.1'] def _main(): @@ -45,7 +45,7 @@ def _main(): maintainer_email="david.pursehouse@sonymobile.com", url="https://github.com/sonyxperiadev/pygerrit", packages=find_packages(), - keywords='gerrit, json, ssh, stream', + keywords='gerrit, json, rest, http', install_requires=REQUIRES, classifiers=( 'Development Status :: 3 - Alpha', diff --git a/testdata/change-abandoned-event.txt b/testdata/change-abandoned-event.txt deleted file mode 100644 index 879ea28..0000000 --- a/testdata/change-abandoned-event.txt +++ /dev/null @@ -1,13 +0,0 @@ -{"type":"change-abandoned", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "abandoner":{"name":"Abandoner Name", - "email":"abandoner@example.com"}, - "reason":"Abandon reason"} diff --git a/testdata/change-merged-event.txt b/testdata/change-merged-event.txt deleted file mode 100644 index 4da678a..0000000 --- a/testdata/change-merged-event.txt +++ /dev/null @@ -1,18 +0,0 @@ -{"type":"change-merged", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "patchSet":{"number":"4", - "revision":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ref":"refs/changes/56/123456/4", - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}, - "createdOn":1341370514}, - "submitter":{"name":"Submitter Name", - "email":"submitter@example.com"}} diff --git a/testdata/change-restored-event.txt b/testdata/change-restored-event.txt deleted file mode 100644 index e0300a8..0000000 --- a/testdata/change-restored-event.txt +++ /dev/null @@ -1,13 +0,0 @@ -{"type":"change-restored", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "restorer":{"name":"Restorer Name", - "email":"restorer@example.com"}, - "reason":"Restore reason"} diff --git a/testdata/comment-added-event.txt b/testdata/comment-added-event.txt deleted file mode 100644 index 9ab50f7..0000000 --- a/testdata/comment-added-event.txt +++ /dev/null @@ -1,25 +0,0 @@ -{"type":"comment-added", -"change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "patchSet":{"number":"4", - "revision":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ref":"refs/changes/56/123456/4", - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}, - "createdOn":1341370514}, - "author":{"name":"Author Name", - "email":"author@example.com"}, - "approvals":[{"type":"CRVW", - "description":"Code Review", - "value":"1"}, - {"type":"VRIF", - "description":"Verified", - "value":"1"}], - "comment":"Review comment"} diff --git a/testdata/draft-published-event.txt b/testdata/draft-published-event.txt deleted file mode 100644 index 8c3453a..0000000 --- a/testdata/draft-published-event.txt +++ /dev/null @@ -1,18 +0,0 @@ -{"type":"draft-published", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "patchSet":{"number":"4", - "revision":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ref":"refs/changes/56/123456/4", - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}, - "createdOn":1342075181}, - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}} diff --git a/testdata/invalid-json.txt b/testdata/invalid-json.txt deleted file mode 100644 index f7a60bb..0000000 --- a/testdata/invalid-json.txt +++ /dev/null @@ -1,4 +0,0 @@ -)]}' -{"type":"user-defined-event", - "title":"Event title", - "description":"Event description"} diff --git a/testdata/merge-failed-event.txt b/testdata/merge-failed-event.txt deleted file mode 100644 index 2d29d29..0000000 --- a/testdata/merge-failed-event.txt +++ /dev/null @@ -1,19 +0,0 @@ -{"type":"merge-failed", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "patchSet":{"number":"4", - "revision":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ref":"refs/changes/56/123456/4", - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}, - "createdOn":1341370514}, - "submitter":{"name":"Submitter Name", - "email":"submitter@example.com"}, - "reason":"Merge failed reason"} diff --git a/testdata/patchset-created-event.txt b/testdata/patchset-created-event.txt deleted file mode 100644 index 2c464be..0000000 --- a/testdata/patchset-created-event.txt +++ /dev/null @@ -1,18 +0,0 @@ -{"type":"patchset-created", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "patchSet":{"number":"4", - "revision":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ref":"refs/changes/56/123456/4", - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}, - "createdOn":1342075181}, - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}}
\ No newline at end of file diff --git a/testdata/ref-updated-event.txt b/testdata/ref-updated-event.txt deleted file mode 100644 index 61944cc..0000000 --- a/testdata/ref-updated-event.txt +++ /dev/null @@ -1,7 +0,0 @@ -{"type":"ref-updated", - "submitter":{"name":"Submitter Name", - "email":"submitter@example.com"}, - "refUpdate":{"oldRev":"0000000000000000000000000000000000000000", - "newRev":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "refName":"refs/tags/refname", - "project":"project-name"}} diff --git a/testdata/reviewer-added-event.txt b/testdata/reviewer-added-event.txt deleted file mode 100644 index b460afc..0000000 --- a/testdata/reviewer-added-event.txt +++ /dev/null @@ -1,18 +0,0 @@ -{"type":"reviewer-added", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "patchSet":{"number":"4", - "revision":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ref":"refs/changes/56/123456/4", - "uploader":{"name":"Uploader Name", - "email":"uploader@example.com"}, - "createdOn":1341370514}, - "reviewer":{"name":"Reviewer Name", - "email":"reviewer@example.com"}} diff --git a/testdata/topic-changed-event.txt b/testdata/topic-changed-event.txt deleted file mode 100644 index 1847440..0000000 --- a/testdata/topic-changed-event.txt +++ /dev/null @@ -1,13 +0,0 @@ -{"type":"topic-changed", - "change":{"project":"project-name", - "branch":"branch-name", - "topic":"topic-name", - "id":"Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "number":"123456", - "subject":"Commit message subject", - "owner":{"name":"Owner Name", - "email":"owner@example.com"}, - "url":"http://review.example.com/123456"}, - "changer":{"name":"Changer Name", - "email":"changer@example.com"}, - "oldTopic":"old-topic"} diff --git a/testdata/unhandled-event.txt b/testdata/unhandled-event.txt deleted file mode 100644 index 6824cc8..0000000 --- a/testdata/unhandled-event.txt +++ /dev/null @@ -1,3 +0,0 @@ -{"type":"this-event-is-not-handled", - "title":"Unhandled event title", - "description":"Unhandled event description"} diff --git a/testdata/user-defined-event.txt b/testdata/user-defined-event.txt deleted file mode 100644 index 7f7b65e..0000000 --- a/testdata/user-defined-event.txt +++ /dev/null @@ -1,3 +0,0 @@ -{"type":"user-defined-event", - "title":"Event title", - "description":"Event description"} diff --git a/unittests.py b/unittests.py index fac78e1..5a074ea 100755 --- a/unittests.py +++ b/unittests.py @@ -23,45 +23,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -""" Unit tests for the Gerrit event stream handler and event objects. """ +""" Unit tests for pygerrit. """ -import json -import os import unittest -from pygerrit.events import PatchsetCreatedEvent, \ - RefUpdatedEvent, ChangeMergedEvent, CommentAddedEvent, \ - ChangeAbandonedEvent, ChangeRestoredEvent, \ - DraftPublishedEvent, GerritEventFactory, GerritEvent, UnhandledEvent, \ - ErrorEvent, MergeFailedEvent, ReviewerAddedEvent, TopicChangedEvent -from pygerrit.client import GerritClient from setup import REQUIRES as setup_requires -@GerritEventFactory.register("user-defined-event") -class UserDefinedEvent(GerritEvent): - - """ Dummy event class to test event registration. """ - - def __init__(self, json_data): - super(UserDefinedEvent, self).__init__(json_data) - self.title = json_data['title'] - self.description = json_data['description'] - - -def _create_event(name, gerrit): - """ Create a new event. - - Read the contents of the file specified by `name` and load as JSON - data, then add as an event in the `gerrit` client. - - """ - testfile = open(os.path.join("testdata", name + ".txt")) - data = testfile.read().replace("\n", "") - gerrit.put_event(data) - return data - - class TestConsistentDependencies(unittest.TestCase): """ Verify that dependency package versions are consistent. """ @@ -83,259 +51,5 @@ class TestConsistentDependencies(unittest.TestCase): "listed in requirements.txt and setup.py") -class TestGerritEvents(unittest.TestCase): - def setUp(self): - self.gerrit = GerritClient("review") - - def test_patchset_created(self): - _create_event("patchset-created-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, PatchsetCreatedEvent)) - self.assertEquals(event.name, "patchset-created") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.patchset.number, "4") - self.assertEquals(event.patchset.revision, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.patchset.ref, "refs/changes/56/123456/4") - self.assertEquals(event.patchset.uploader.name, "Uploader Name") - self.assertEquals(event.patchset.uploader.email, "uploader@example.com") - self.assertEquals(event.uploader.name, "Uploader Name") - self.assertEquals(event.uploader.email, "uploader@example.com") - - def test_draft_published(self): - _create_event("draft-published-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, DraftPublishedEvent)) - self.assertEquals(event.name, "draft-published") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.patchset.number, "4") - self.assertEquals(event.patchset.revision, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.patchset.ref, "refs/changes/56/123456/4") - self.assertEquals(event.patchset.uploader.name, "Uploader Name") - self.assertEquals(event.patchset.uploader.email, "uploader@example.com") - self.assertEquals(event.uploader.name, "Uploader Name") - self.assertEquals(event.uploader.email, "uploader@example.com") - - def test_ref_updated(self): - _create_event("ref-updated-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, RefUpdatedEvent)) - self.assertEquals(event.name, "ref-updated") - self.assertEquals(event.ref_update.project, "project-name") - self.assertEquals(event.ref_update.oldrev, - "0000000000000000000000000000000000000000") - self.assertEquals(event.ref_update.newrev, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.ref_update.refname, "refs/tags/refname") - self.assertEquals(event.submitter.name, "Submitter Name") - self.assertEquals(event.submitter.email, "submitter@example.com") - - def test_change_merged(self): - _create_event("change-merged-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, ChangeMergedEvent)) - self.assertEquals(event.name, "change-merged") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.patchset.number, "4") - self.assertEquals(event.patchset.revision, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.patchset.ref, "refs/changes/56/123456/4") - self.assertEquals(event.patchset.uploader.name, "Uploader Name") - self.assertEquals(event.patchset.uploader.email, "uploader@example.com") - self.assertEquals(event.submitter.name, "Submitter Name") - self.assertEquals(event.submitter.email, "submitter@example.com") - - def test_merge_failed(self): - _create_event("merge-failed-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, MergeFailedEvent)) - self.assertEquals(event.name, "merge-failed") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.patchset.number, "4") - self.assertEquals(event.patchset.revision, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.patchset.ref, "refs/changes/56/123456/4") - self.assertEquals(event.patchset.uploader.name, "Uploader Name") - self.assertEquals(event.patchset.uploader.email, "uploader@example.com") - self.assertEquals(event.submitter.name, "Submitter Name") - self.assertEquals(event.submitter.email, "submitter@example.com") - self.assertEquals(event.reason, "Merge failed reason") - - def test_comment_added(self): - _create_event("comment-added-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, CommentAddedEvent)) - self.assertEquals(event.name, "comment-added") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.patchset.number, "4") - self.assertEquals(event.patchset.revision, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.patchset.ref, "refs/changes/56/123456/4") - self.assertEquals(event.patchset.uploader.name, "Uploader Name") - self.assertEquals(event.patchset.uploader.email, "uploader@example.com") - self.assertEquals(len(event.approvals), 2) - self.assertEquals(event.approvals[0].category, "CRVW") - self.assertEquals(event.approvals[0].description, "Code Review") - self.assertEquals(event.approvals[0].value, "1") - self.assertEquals(event.approvals[1].category, "VRIF") - self.assertEquals(event.approvals[1].description, "Verified") - self.assertEquals(event.approvals[1].value, "1") - self.assertEquals(event.author.name, "Author Name") - self.assertEquals(event.author.email, "author@example.com") - - def test_reviewer_added(self): - _create_event("reviewer-added-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, ReviewerAddedEvent)) - self.assertEquals(event.name, "reviewer-added") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.patchset.number, "4") - self.assertEquals(event.patchset.revision, - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.patchset.ref, "refs/changes/56/123456/4") - self.assertEquals(event.patchset.uploader.name, "Uploader Name") - self.assertEquals(event.patchset.uploader.email, "uploader@example.com") - self.assertEquals(event.reviewer.name, "Reviewer Name") - self.assertEquals(event.reviewer.email, "reviewer@example.com") - - def test_change_abandoned(self): - _create_event("change-abandoned-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, ChangeAbandonedEvent)) - self.assertEquals(event.name, "change-abandoned") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.abandoner.name, "Abandoner Name") - self.assertEquals(event.abandoner.email, "abandoner@example.com") - self.assertEquals(event.reason, "Abandon reason") - - def test_change_restored(self): - _create_event("change-restored-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, ChangeRestoredEvent)) - self.assertEquals(event.name, "change-restored") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.restorer.name, "Restorer Name") - self.assertEquals(event.restorer.email, "restorer@example.com") - self.assertEquals(event.reason, "Restore reason") - - def test_topic_changed(self): - _create_event("topic-changed-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, TopicChangedEvent)) - self.assertEquals(event.name, "topic-changed") - self.assertEquals(event.change.project, "project-name") - self.assertEquals(event.change.branch, "branch-name") - self.assertEquals(event.change.topic, "topic-name") - self.assertEquals(event.change.change_id, - "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - self.assertEquals(event.change.number, "123456") - self.assertEquals(event.change.subject, "Commit message subject") - self.assertEquals(event.change.url, "http://review.example.com/123456") - self.assertEquals(event.change.owner.name, "Owner Name") - self.assertEquals(event.change.owner.email, "owner@example.com") - self.assertEquals(event.changer.name, "Changer Name") - self.assertEquals(event.changer.email, "changer@example.com") - self.assertEquals(event.oldtopic, "old-topic") - - def test_user_defined_event(self): - _create_event("user-defined-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, UserDefinedEvent)) - self.assertEquals(event.title, "Event title") - self.assertEquals(event.description, "Event description") - - def test_unhandled_event(self): - data = _create_event("unhandled-event", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, UnhandledEvent)) - self.assertEquals(event.json, json.loads(data)) - - def test_invalid_json(self): - _create_event("invalid-json", self.gerrit) - event = self.gerrit.get_event(False) - self.assertTrue(isinstance(event, ErrorEvent)) - - def test_add_duplicate_event(self): - try: - @GerritEventFactory.register("user-defined-event") - class AnotherUserDefinedEvent(GerritEvent): - pass - except: - return - self.fail("Did not raise exception when duplicate event registered") - if __name__ == '__main__': unittest.main() |