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

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

Powered by Google App Engine
This is Rietveld 408576698