1from __future__ import print_function, unicode_literals
2
3import logging
4import subprocess
5
6from rbtools.api.client import RBClient
7from rbtools.api.errors import APIError, ServerInterfaceError
8
9
10SUBMITTED = 'submitted'
11
12
13class HookError(Exception):
14    pass
15
16
17def get_api(server_url, **kwargs):
18    """Returns an RBClient instance and the associated root resource.
19
20    Hooks should use this method to gain access to the API, instead of
21    instantiating their own client.
22
23    Args:
24        server_url (unicode):
25            The server URL to retrieve.
26
27        **kwargs (dict):
28            Additional keyword arguments to pass to the
29            :py:class:`~rbtools.api.client.RBClient` constructor. See
30            :py:meth:`SyncTransport.__init__()
31            <rbtools.api.transport.sync.SyncTransport.__init__>` for arguments
32            that are accepted.
33
34    Returns:
35        tuple:
36        This returns a 2-tuple of the :py:class:`~rbtools.api.client.RBClient`
37        and :py:class:`<root resource> rbtools.api.resource.Resource`.
38    """
39    api_client = RBClient(server_url, **kwargs)
40
41    try:
42        api_root = api_client.get_root()
43    except ServerInterfaceError as e:
44        raise HookError('Could not reach the Review Board server at %s: %s'
45                        % (server_url, e))
46    except APIError as e:
47        raise HookError('Unexpected API Error: %s' % e)
48
49    return api_client, api_root
50
51
52def execute(command):
53    """Executes the specified command and returns the stdout output."""
54    process = subprocess.Popen(command, stdout=subprocess.PIPE)
55    output = process.communicate()[0].strip()
56
57    if process.returncode:
58        logging.warning('Failed to execute command: %s', command)
59        return None
60
61    return output
62
63
64def initialize_logging():
65    """Sets up a log handler to format log messages.
66
67    Warning, error, and critical messages will show the level name as a prefix,
68    followed by the message.
69    """
70    root = logging.getLogger()
71
72    handler = logging.StreamHandler()
73    handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
74    handler.setLevel(logging.WARNING)
75    root.addHandler(handler)
76
77
78def get_review_request_id(regex, commit_message):
79    """Returns the review request ID referenced in the commit message.
80
81    We assume there is at most one review request associated with each commit.
82    If a matching review request cannot be found, we return 0.
83    """
84    match = regex.search(commit_message)
85    return (match and int(match.group('id'))) or 0
86
87
88def get_review_request(review_request_id, api_root):
89    """Returns the review request resource for the given ID."""
90    try:
91        review_request = api_root.get_review_request(
92            review_request_id=review_request_id)
93    except APIError as e:
94        raise HookError('Error getting review request: %s' % e)
95
96    return review_request
97
98
99def close_review_request(server_url, username, password, review_request_id,
100                         description):
101    """Closes the specified review request as submitted."""
102    api_client, api_root = get_api(server_url, username, password)
103    review_request = get_review_request(review_request_id, api_root)
104
105    if review_request.status == SUBMITTED:
106        logging.warning('Review request #%s is already %s.',
107                        review_request_id, SUBMITTED)
108        return
109
110    if description:
111        review_request = review_request.update(status=SUBMITTED,
112                                               description=description)
113    else:
114        review_request = review_request.update(status=SUBMITTED)
115
116    print('Review request #%s is set to %s.' %
117          (review_request_id, review_request.status))
118
119
120def get_review_request_approval(server_url, username, password,
121                                review_request_id):
122    """Returns the approval information for the given review request."""
123    api_client, api_root = get_api(server_url, username, password)
124    review_request = get_review_request(review_request_id, api_root)
125
126    return review_request.approved, review_request.approval_failure
127