blob: e3f50d4687cdc687832b48b33ff21bf7f1341f3a [file] [log] [blame]
hinoka@chromium.org7a790542014-12-10 02:04:39 +00001#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Run a pinned gsutil."""
7
8
9import argparse
10import shutil
11import zipfile
12import hashlib
13import base64
14import os
15import sys
16import json
17import urllib
18import subprocess
19
20
21GSUTIL_URL = 'https://storage.googleapis.com/pub/'
22API_URL = 'https://www.googleapis.com/storage/v1/b/pub/o/'
23
24THIS_DIR = os.path.dirname(os.path.abspath(__file__))
25DEFAULT_BIN_DIR = os.path.join(THIS_DIR, 'external_bin', 'gsutil')
26DEFAULT_FALLBACK_GSUTIL = os.path.join(
27 THIS_DIR, 'third_party', 'gsutil', 'gsutil')
28
29
30class SubprocessError(Exception):
31 pass
32
33
34class InvalidGsutilError(Exception):
35 pass
36
37
38def call(args, verbose=True, **kwargs):
39 kwargs['stdout'] = subprocess.PIPE
40 kwargs['stderr'] = subprocess.STDOUT
41 proc = subprocess.Popen(args, **kwargs)
42 out = []
43 for line in proc.stdout:
44 out.append(line)
45 if verbose:
46 sys.stdout.write(line)
47 code = proc.wait()
48 if code:
49 raise SubprocessError('%s failed with %s' % (args, code))
50 return ''.join(out)
51
52
53def download_gsutil(version, target_dir):
54 """Downloads gsutil into the target_dir."""
55 filename = 'gsutil_%s.zip' % version
56 target_filename = os.path.join(target_dir, filename)
57
58 # Check if the target exists already.
59 if os.path.exists(target_filename):
60 md5_calc = hashlib.md5()
61 with open(target_filename, 'rb') as f:
62 while True:
63 buf = f.read(4096)
64 if not buf:
65 break
66 md5_calc.update(buf)
67 local_md5 = md5_calc.hexdigest()
68
69 metadata_url = '%s%s' % (API_URL, filename)
70 metadata = json.load(urllib.urlopen(metadata_url))
71 remote_md5 = base64.b64decode(metadata['md5Hash'])
72
73 if local_md5 == remote_md5:
74 return target_filename
75 os.remove(target_filename)
76
77 # Do the download.
78 url = '%s%s' % (GSUTIL_URL, filename)
79 u = urllib.urlopen(url)
80 with open(target_filename, 'wb') as f:
81 while True:
82 buf = u.read(4096)
83 if not buf:
84 break
85 f.write(buf)
86 return target_filename
87
88
89def check_gsutil(gsutil_bin):
90 """Run gsutil version and make sure it runs."""
91 try:
92 call([sys.executable, gsutil_bin, 'version'], verbose=False)
93 return True
94 except SubprocessError:
95 return False
96
97
98def ensure_gsutil(version, target):
99 bin_dir = os.path.join(target, 'gsutil_%s' % version)
100 gsutil_bin = os.path.join(bin_dir, 'gsutil', 'gsutil')
101 if os.path.isfile(gsutil_bin) and check_gsutil(gsutil_bin):
102 # Everything is awesome! we're all done here.
103 return gsutil_bin
104
105 if os.path.isdir(bin_dir):
106 # Clean up if we're redownloading a corrupted gsutil.
107 shutil.rmtree(bin_dir)
108 cache_dir = os.path.join(target, '.cache_dir')
109 if not os.path.isdir(cache_dir):
110 os.makedirs(cache_dir)
111 target_zip_filename = download_gsutil(version, cache_dir)
112 with zipfile.ZipFile(target_zip_filename, 'r') as target_zip:
113 target_zip.extractall(bin_dir)
114
115 # Final check that the gsutil bin is okay. This should never fail.
116 if not check_gsutil(gsutil_bin):
117 raise InvalidGsutilError()
118
119 return gsutil_bin
120
121
122def run_gsutil(force_version, fallback, target, args):
123 if force_version:
124 gsutil_bin = ensure_gsutil(force_version, target)
125 else:
126 gsutil_bin = fallback
127 cmd = [sys.executable, gsutil_bin] + args
128 call(cmd)
129
130
131def parse_args():
132 parser = argparse.ArgumentParser()
133 parser.add_argument('--force-version')
134 parser.add_argument('--fallback', default=DEFAULT_FALLBACK_GSUTIL)
135 parser.add_argument('--target', default=DEFAULT_BIN_DIR)
136 parser.add_argument('args', nargs=argparse.REMAINDER)
137
138 args = parser.parse_args()
139 return args.force_version, args.fallback, args.target, args.args
140
141
142def main():
143 force_version, fallback, target, args = parse_args()
144 run_gsutil(force_version, fallback, target, args)
145
146if __name__ == '__main__':
147 sys.exit(main())