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

Side by Side Diff: scripts/slave/recipe_modules/cipd/resources/bootstrap.py

Issue 1193813004: cipd recipe_module (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: coverage -> 100% Created 5 years, 5 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
« no previous file with comments | « scripts/slave/recipe_modules/cipd/example.expected/install-failed.json ('k') | 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
(Empty)
1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import errno
6 import hashlib
7 import httplib
8 import json
9 import os
10 import sys
11 import tempfile
12 import time
13 import traceback
14 import urllib
15 import urllib2
16
17
18 # Default package repository URL.
19 CIPD_BACKEND_URL = 'https://chrome-infra-packages.appspot.com'
20 CLIENT_VERSIONS = {
21 'linux-amd64': '9579504cec0336688292f5b0b68c3ed4e288e273',
22 }
23
24
25 class CipdBootstrapError(Exception):
26 """Raised by install_cipd_client on fatal error."""
27
28 def install_cipd_client(path, package, version):
29 """Installs CIPD client to <path>/cipd.
30
31 Args:
32 path: root directory to install CIPD client into.
33 package: cipd client package name, e.g. infra/tools/cipd/linux-amd64.
34 version: version of the package to install.
35
36 Returns:
37 Absolute path to CIPD executable.
38 """
39 print('Ensuring CIPD client is up-to-date')
40 version_file = os.path.join(path, 'VERSION')
41 bin_file = os.path.join(path, 'cipd')
42
43 # Resolve version to concrete instance ID, e.g "live" -> "abcdef0123....".
44 instance_id = call_cipd_api(
45 'repo/v1/instance/resolve',
46 {'package_name': package, 'version': version})['instance_id']
47 print('CIPD client %s => %s', version, instance_id)
48
49 # Already installed?
50 installed_instance_id = (read_file(version_file) or '').strip()
51 if installed_instance_id == instance_id and os.path.exists(bin_file):
52 return bin_file
53
54 # Resolve instance ID to an URL to fetch client binary from.
55 client_info = call_cipd_api(
56 'repo/v1/client',
57 {'package_name': package, 'instance_id': instance_id})
58 print('CIPD client binary info:\n%s', dump_json(client_info))
59
60 # Fetch the client. It is ~10 MB, so don't bother and fetch it into memory.
61 status, raw_client_bin = fetch_url(client_info['client_binary']['fetch_url'])
62 if status != 200:
63 print('Failed to fetch client binary, HTTP %d' % status)
64 raise CipdBootstrapError('Failed to fetch client binary, HTTP %d' % status)
65 digest = hashlib.sha1(raw_client_bin).hexdigest()
66 if digest != client_info['client_binary']['sha1']:
67 raise CipdBootstrapError('Client SHA1 mismatch')
68
69 # Success.
70 print('Fetched CIPD client %s:%s at %s', package, instance_id, bin_file)
71 write_file(bin_file, raw_client_bin)
72 os.chmod(bin_file, 0755)
73 write_file(version_file, instance_id + '\n')
74 return bin_file
75
76
77 def call_cipd_api(endpoint, query):
78 """Sends GET request to CIPD backend, parses JSON response."""
79 url = '%s/_ah/api/%s' % (CIPD_BACKEND_URL, endpoint)
80 if query:
81 url += '?' + urllib.urlencode(query)
82 status, body = fetch_url(url)
83 if status != 200:
84 raise CipdBootstrapError('Server replied with HTTP %d' % status)
85 try:
86 body = json.loads(body)
87 except ValueError:
88 raise CipdBootstrapError('Server returned invalid JSON')
89 status = body.get('status')
90 if status != 'SUCCESS':
91 m = body.get('error_message') or '<no error message>'
92 raise CipdBootstrapError('Server replied with error %s: %s' % (status, m))
93 return body
94
95
96 def fetch_url(url, headers=None):
97 """Sends GET request (with retries).
98
99 Args:
100 url: URL to fetch.
101 headers: dict with request headers.
102
103 Returns:
104 (200, reply body) on success.
105 (HTTP code, None) on HTTP 401, 403, or 404 reply.
106
107 Raises:
108 Whatever urllib2 raises.
109 """
110 req = urllib2.Request(url)
111 req.add_header('User-Agent', 'cipd recipe bootstrap.py')
112 for k, v in (headers or {}).iteritems():
113 req.add_header(str(k), str(v))
114 i = 0
115 while True:
116 i += 1
117 try:
118 print('GET %s', url)
119 return 200, urllib2.urlopen(req, timeout=60).read()
120 except Exception as e:
121 if isinstance(e, urllib2.HTTPError):
122 print('Failed to fetch %s, server returned HTTP %d', url, e.code)
123 if e.code in (401, 403, 404):
124 return e.code, None
125 else:
126 print('Failed to fetch %s', url)
127 if i == 20:
128 raise
129 print('Retrying in %d sec.', i)
130 time.sleep(i)
131
132
133 def ensure_directory(path):
134 """Creates a directory."""
135 # Handle a case where a file is being converted into a directory.
136 chunks = path.split(os.sep)
137 for i in xrange(len(chunks)):
138 p = os.sep.join(chunks[:i+1])
139 if os.path.exists(p) and not os.path.isdir(p):
140 os.remove(p)
141 break
142 try:
143 os.makedirs(path)
144 except OSError as e:
145 if e.errno != errno.EEXIST:
146 raise
147
148
149 def read_file(path):
150 """Returns contents of a file or None if missing."""
151 try:
152 with open(path, 'r') as f:
153 return f.read()
154 except IOError as e:
155 if e.errno == errno.ENOENT:
156 return None
157 raise
158
159
160 def write_file(path, data):
161 """Puts a file on disk, atomically."""
162 assert sys.platform in ('linux2', 'darwin')
163 ensure_directory(os.path.dirname(path))
164 fd, temp_file = tempfile.mkstemp(dir=os.path.dirname(path))
165 with os.fdopen(fd, 'w') as f:
166 f.write(data)
167 os.rename(temp_file, path)
168
169
170 def dump_json(obj):
171 """Pretty-formats object to JSON."""
172 return json.dumps(obj, indent=2, sort_keys=True, separators=(',',':'))
173
174
175 def main():
176 data = json.load(sys.stdin)
177 package = "infra/tools/cipd/%s" % data['platform']
178 version = CLIENT_VERSIONS[data['platform']]
179 bin_path = data['bin_path']
180
181 # return if this client version is already installed.
182 exe_path = os.path.join(bin_path, 'cipd')
183 try:
184 if not os.path.isfile(exe_path):
185 out = install_cipd_client(bin_path, package, version)
186 assert out == exe_path
187 except Exception as e:
188 print ("Exception installing cipd: %s" % e)
189 exc_type, exc_value, exc_traceback = sys.exc_info()
190 traceback.print_tb(exc_traceback)
191 return 1
192
193 return 0
194
195 if __name__ == '__main__':
196 sys.exit(main())
OLDNEW
« no previous file with comments | « scripts/slave/recipe_modules/cipd/example.expected/install-failed.json ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698