Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(91)

Side by Side Diff: gerrit_util.py

Issue 1350673004: gerrit_util: Add GCE metadata server auth. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Log retries at INFO. Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """ 5 """
6 Utilities for requesting information for a gerrit server via https. 6 Utilities for requesting information for a gerrit server via https.
7 7
8 https://gerrit-review.googlesource.com/Documentation/rest-api.html 8 https://gerrit-review.googlesource.com/Documentation/rest-api.html
9 """ 9 """
10 10
11 import base64 11 import base64
12 import httplib 12 import httplib
13 import json 13 import json
14 import logging 14 import logging
15 import netrc 15 import netrc
16 import os 16 import os
17 import re 17 import re
18 import socket
18 import stat 19 import stat
19 import sys 20 import sys
20 import time 21 import time
21 import urllib 22 import urllib
23 import urlparse
22 from cStringIO import StringIO 24 from cStringIO import StringIO
23 25
24 _netrc_file = '_netrc' if sys.platform.startswith('win') else '.netrc'
25 _netrc_file = os.path.join(os.environ['HOME'], _netrc_file)
26 try:
27 NETRC = netrc.netrc(_netrc_file)
28 except IOError:
29 print >> sys.stderr, 'WARNING: Could not read netrc file %s' % _netrc_file
30 NETRC = netrc.netrc(os.devnull)
31 except netrc.NetrcParseError as e:
32 _netrc_stat = os.stat(e.filename)
33 if _netrc_stat.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
34 print >> sys.stderr, (
35 'WARNING: netrc file %s cannot be used because its file permissions '
36 'are insecure. netrc file permissions should be 600.' % _netrc_file)
37 else:
38 print >> sys.stderr, ('ERROR: Cannot use netrc file %s due to a parsing '
39 'error.' % _netrc_file)
40 raise
41 del _netrc_stat
42 NETRC = netrc.netrc(os.devnull)
43 del _netrc_file
44 26
45 LOGGER = logging.getLogger() 27 LOGGER = logging.getLogger()
46 TRY_LIMIT = 5 28 TRY_LIMIT = 5
47 29
30
48 # Controls the transport protocol used to communicate with gerrit. 31 # Controls the transport protocol used to communicate with gerrit.
49 # This is parameterized primarily to enable GerritTestCase. 32 # This is parameterized primarily to enable GerritTestCase.
50 GERRIT_PROTOCOL = 'https' 33 GERRIT_PROTOCOL = 'https'
51 34
52 35
53 class GerritError(Exception): 36 class GerritError(Exception):
54 """Exception class for errors commuicating with the gerrit-on-borg service.""" 37 """Exception class for errors commuicating with the gerrit-on-borg service."""
55 def __init__(self, http_status, *args, **kwargs): 38 def __init__(self, http_status, *args, **kwargs):
56 super(GerritError, self).__init__(*args, **kwargs) 39 super(GerritError, self).__init__(*args, **kwargs)
57 self.http_status = http_status 40 self.http_status = http_status
(...skipping 19 matching lines...) Expand all
77 protocol = GERRIT_PROTOCOL 60 protocol = GERRIT_PROTOCOL
78 if protocol == 'https': 61 if protocol == 'https':
79 return httplib.HTTPSConnection 62 return httplib.HTTPSConnection
80 elif protocol == 'http': 63 elif protocol == 'http':
81 return httplib.HTTPConnection 64 return httplib.HTTPConnection
82 else: 65 else:
83 raise RuntimeError( 66 raise RuntimeError(
84 "Don't know how to work with protocol '%s'" % protocol) 67 "Don't know how to work with protocol '%s'" % protocol)
85 68
86 69
70 class Authenticator(object):
71 """Base authenticator class for authenticator implementations to subclass."""
72
73 def get_auth_header(self, host):
74 raise NotImplementedError()
75
76 @staticmethod
77 def get():
78 """Returns: (Authenticator) The identified Authenticator to use.
79
80 Probes the local system and its environment and identifies the
81 Authenticator instance to use.
82 """
83 if GceAuthenticator.is_gce():
84 return GceAuthenticator()
85 return NetrcAuthenticator()
86
87
88 class NetrcAuthenticator(Authenticator):
89 """Authenticator implementation that uses ".netrc" for token.
90 """
91
92 def __init__(self):
93 self.netrc = self._get_netrc()
94
95 @staticmethod
96 def _get_netrc():
97 path = '_netrc' if sys.platform.startswith('win') else '.netrc'
98 path = os.path.join(os.environ['HOME'], path)
99 try:
100 return netrc.netrc(path)
101 except IOError:
102 print >> sys.stderr, 'WARNING: Could not read netrc file %s' % path
103 return netrc.netrc(os.devnull)
104 except netrc.NetrcParseError as e:
105 st = os.stat(e.path)
106 if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
107 print >> sys.stderr, (
108 'WARNING: netrc file %s cannot be used because its file '
109 'permissions are insecure. netrc file permissions should be '
110 '600.' % path)
111 else:
112 print >> sys.stderr, ('ERROR: Cannot use netrc file %s due to a '
113 'parsing error.' % path)
114 raise
115 return netrc.netrc(os.devnull)
116
117 def get_auth_header(self, host):
118 auth = self.netrc.authenticators(host)
119 if auth:
120 return 'Basic %s' % (base64.b64encode('%s:%s' % (auth[0], auth[2])))
121 return None
122
123
124 class GceAuthenticator(Authenticator):
125 """Authenticator implementation that uses GCE metadata service for token.
126 """
127
128 _INFO_URL = 'http://metadata.google.internal'
129 _ACQUIRE_URL = ('http://metadata/computeMetadata/v1/instance/'
130 'service-accounts/default/token')
131 _ACQUIRE_HEADERS = {"Metadata-Flavor": "Google"}
132
133 _cache_is_gce = None
134 _token_cache = None
135 _token_expiration = None
136
137 @classmethod
138 def is_gce(cls):
139 if cls._cache_is_gce is None:
140 cls._cache_is_gce = cls._test_is_gce()
141 return cls._cache_is_gce
142
143 @classmethod
144 def _test_is_gce(cls):
145 # Based on https://cloud.google.com/compute/docs/metadata#runninggce
146 try:
147 resp = cls._get(cls._INFO_URL)
148 except socket.error:
149 # Could not resolve URL.
150 return False
151 return resp.getheader('Metadata-Flavor', None) == 'Google'
152
153 @staticmethod
154 def _get(url, **kwargs):
155 next_delay_sec = 1
156 for i in xrange(TRY_LIMIT):
157 if i > 0:
158 # Retry server error status codes.
159 LOGGER.info('Encountered server error; retrying after %d second(s).',
160 next_delay_sec)
161 time.sleep(next_delay_sec)
162 next_delay_sec *= 2
163
164 p = urlparse.urlparse(url)
165 c = GetConnectionClass(protocol=p.scheme)(p.netloc)
166 c.request('GET', url, **kwargs)
167 resp = c.getresponse()
168 LOGGER.debug('GET [%s] #%d/%d (%d)', url, i+1, TRY_LIMIT, resp.status)
169 if resp.status < httplib.INTERNAL_SERVER_ERROR:
170 return resp
171
172
173 @classmethod
174 def _get_token_dict(cls):
175 if cls._token_cache:
176 # If it expires within 25 seconds, refresh.
177 if cls._token_expiration < time.time() - 25:
178 return cls._token_cache
179
180 resp = cls._get(cls._ACQUIRE_URL, headers=cls._ACQUIRE_HEADERS)
181 if resp.status != httplib.OK:
182 return None
183 cls._token_cache = json.load(resp)
184 cls._token_expiration = cls._token_cache['expires_in'] + time.time()
185 return cls._token_cache
186
187 def get_auth_header(self, _host):
188 token_dict = self._get_token_dict()
189 if not token_dict:
190 return None
191 return '%(token_type)s %(access_token)s' % token_dict
192
193
194
87 def CreateHttpConn(host, path, reqtype='GET', headers=None, body=None): 195 def CreateHttpConn(host, path, reqtype='GET', headers=None, body=None):
88 """Opens an https connection to a gerrit service, and sends a request.""" 196 """Opens an https connection to a gerrit service, and sends a request."""
89 headers = headers or {} 197 headers = headers or {}
90 bare_host = host.partition(':')[0] 198 bare_host = host.partition(':')[0]
91 auth = NETRC.authenticators(bare_host)
92 199
200 auth = Authenticator.get().get_auth_header(bare_host)
93 if auth: 201 if auth:
94 headers.setdefault('Authorization', 'Basic %s' % ( 202 headers.setdefault('Authorization', auth)
95 base64.b64encode('%s:%s' % (auth[0], auth[2]))))
96 else: 203 else:
97 LOGGER.debug('No authorization found in netrc for %s.' % bare_host) 204 LOGGER.debug('No authorization found for %s.' % bare_host)
98 205
99 if 'Authorization' in headers and not path.startswith('a/'): 206 if 'Authorization' in headers and not path.startswith('a/'):
100 url = '/a/%s' % path 207 url = '/a/%s' % path
101 else: 208 else:
102 url = '/%s' % path 209 url = '/%s' % path
103 210
104 if body: 211 if body:
105 body = json.JSONEncoder().encode(body) 212 body = json.JSONEncoder().encode(body)
106 headers.setdefault('Content-Type', 'application/json') 213 headers.setdefault('Content-Type', 'application/json')
107 if LOGGER.isEnabledFor(logging.DEBUG): 214 if LOGGER.isEnabledFor(logging.DEBUG):
(...skipping 357 matching lines...) Expand 10 before | Expand all | Expand 10 after
465 username = review.get('email', jmsg.get('name', '')) 572 username = review.get('email', jmsg.get('name', ''))
466 raise GerritError(200, 'Unable to set %s label for user "%s"' 573 raise GerritError(200, 'Unable to set %s label for user "%s"'
467 ' on change %s.' % (label, username, change)) 574 ' on change %s.' % (label, username, change))
468 jmsg = GetChangeCurrentRevision(host, change) 575 jmsg = GetChangeCurrentRevision(host, change)
469 if not jmsg: 576 if not jmsg:
470 raise GerritError( 577 raise GerritError(
471 200, 'Could not get review information for change "%s"' % change) 578 200, 'Could not get review information for change "%s"' % change)
472 elif jmsg[0]['current_revision'] != revision: 579 elif jmsg[0]['current_revision'] != revision:
473 raise GerritError(200, 'While resetting labels on change "%s", ' 580 raise GerritError(200, 'While resetting labels on change "%s", '
474 'a new patchset was uploaded.' % change) 581 'a new patchset was uploaded.' % change)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698