blob: d57cafb94c3365f021996167c048097a0727bc0c [file] [log] [blame]
[email protected]7a790542014-12-10 02:04:391#!/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
[email protected]7a790542014-12-10 02:04:3910import base64
[email protected]605d81d2015-09-18 22:33:5311import contextlib
[email protected]df351762014-12-18 11:12:3412import hashlib
[email protected]7a790542014-12-10 02:04:3913import json
[email protected]df351762014-12-18 11:12:3414import os
15import shutil
[email protected]7a790542014-12-10 02:04:3916import subprocess
[email protected]df351762014-12-18 11:12:3417import sys
[email protected]605d81d2015-09-18 22:33:5318import tempfile
19import time
[email protected]df351762014-12-18 11:12:3420import urllib2
21import zipfile
[email protected]7a790542014-12-10 02:04:3922
23
24GSUTIL_URL = 'https://storage.googleapis.com/pub/'
25API_URL = 'https://www.googleapis.com/storage/v1/b/pub/o/'
26
27THIS_DIR = os.path.dirname(os.path.abspath(__file__))
28DEFAULT_BIN_DIR = os.path.join(THIS_DIR, 'external_bin', 'gsutil')
29DEFAULT_FALLBACK_GSUTIL = os.path.join(
30 THIS_DIR, 'third_party', 'gsutil', 'gsutil')
31
[email protected]7a790542014-12-10 02:04:3932class InvalidGsutilError(Exception):
33 pass
34
35
[email protected]7a790542014-12-10 02:04:3936def download_gsutil(version, target_dir):
37 """Downloads gsutil into the target_dir."""
38 filename = 'gsutil_%s.zip' % version
39 target_filename = os.path.join(target_dir, filename)
40
41 # Check if the target exists already.
42 if os.path.exists(target_filename):
43 md5_calc = hashlib.md5()
44 with open(target_filename, 'rb') as f:
45 while True:
46 buf = f.read(4096)
47 if not buf:
48 break
49 md5_calc.update(buf)
50 local_md5 = md5_calc.hexdigest()
51
52 metadata_url = '%s%s' % (API_URL, filename)
[email protected]df351762014-12-18 11:12:3453 metadata = json.load(urllib2.urlopen(metadata_url))
[email protected]7a790542014-12-10 02:04:3954 remote_md5 = base64.b64decode(metadata['md5Hash'])
55
56 if local_md5 == remote_md5:
57 return target_filename
58 os.remove(target_filename)
59
60 # Do the download.
61 url = '%s%s' % (GSUTIL_URL, filename)
[email protected]df351762014-12-18 11:12:3462 u = urllib2.urlopen(url)
[email protected]7a790542014-12-10 02:04:3963 with open(target_filename, 'wb') as f:
64 while True:
65 buf = u.read(4096)
66 if not buf:
67 break
68 f.write(buf)
69 return target_filename
70
71
72def check_gsutil(gsutil_bin):
73 """Run gsutil version and make sure it runs."""
[email protected]5e879a42015-01-24 00:55:4674 return subprocess.call(
75 [sys.executable, gsutil_bin, 'version'],
76 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) == 0
[email protected]7a790542014-12-10 02:04:3977
[email protected]605d81d2015-09-18 22:33:5378@contextlib.contextmanager
79def temporary_directory(base):
80 tmpdir = tempfile.mkdtemp(prefix='gsutil_py', dir=base)
81 try:
82 yield tmpdir
83 finally:
84 if os.path.isdir(tmpdir):
85 shutil.rmtree(tmpdir)
86
87def ensure_gsutil(version, target, clean):
[email protected]7a790542014-12-10 02:04:3988 bin_dir = os.path.join(target, 'gsutil_%s' % version)
89 gsutil_bin = os.path.join(bin_dir, 'gsutil', 'gsutil')
[email protected]605d81d2015-09-18 22:33:5390 if not clean and os.path.isfile(gsutil_bin) and check_gsutil(gsutil_bin):
[email protected]7a790542014-12-10 02:04:3991 # Everything is awesome! we're all done here.
92 return gsutil_bin
93
[email protected]605d81d2015-09-18 22:33:5394 if not os.path.exists(target):
95 os.makedirs(target)
96 with temporary_directory(target) as instance_dir:
[email protected]7a790542014-12-10 02:04:3997 # Clean up if we're redownloading a corrupted gsutil.
[email protected]605d81d2015-09-18 22:33:5398 cleanup_path = os.path.join(instance_dir, 'clean')
99 try:
100 os.rename(bin_dir, cleanup_path)
101 except (OSError, IOError):
102 cleanup_path = None
103 if cleanup_path:
104 shutil.rmtree(cleanup_path)
105
106 download_dir = os.path.join(instance_dir, 'download')
107 target_zip_filename = download_gsutil(version, instance_dir)
108 with zipfile.ZipFile(target_zip_filename, 'r') as target_zip:
109 target_zip.extractall(download_dir)
110
111 try:
112 os.rename(download_dir, bin_dir)
113 except (OSError, IOError):
114 # Something else did this in parallel.
115 pass
[email protected]7a790542014-12-10 02:04:39116
117 # Final check that the gsutil bin is okay. This should never fail.
118 if not check_gsutil(gsutil_bin):
119 raise InvalidGsutilError()
[email protected]7a790542014-12-10 02:04:39120 return gsutil_bin
121
122
[email protected]605d81d2015-09-18 22:33:53123def run_gsutil(force_version, fallback, target, args, clean=False):
[email protected]7a790542014-12-10 02:04:39124 if force_version:
[email protected]605d81d2015-09-18 22:33:53125 gsutil_bin = ensure_gsutil(force_version, target, clean)
[email protected]7a790542014-12-10 02:04:39126 else:
127 gsutil_bin = fallback
[email protected]fdb9ce32016-04-05 23:57:12128 disable_update = ['-o', 'GSUtil:software_update_check_period=0']
129 cmd = [sys.executable, gsutil_bin] + disable_update + args
[email protected]5e879a42015-01-24 00:55:46130 return subprocess.call(cmd)
[email protected]7a790542014-12-10 02:04:39131
132
133def parse_args():
[email protected]605d81d2015-09-18 22:33:53134 bin_dir = os.environ.get('DEPOT_TOOLS_GSUTIL_BIN_DIR', DEFAULT_BIN_DIR)
135
[email protected]7a790542014-12-10 02:04:39136 parser = argparse.ArgumentParser()
[email protected]493270e2015-07-15 19:37:31137 parser.add_argument('--force-version', default='4.13')
[email protected]605d81d2015-09-18 22:33:53138 parser.add_argument('--clean', action='store_true',
139 help='Clear any existing gsutil package, forcing a new download.')
[email protected]7a790542014-12-10 02:04:39140 parser.add_argument('--fallback', default=DEFAULT_FALLBACK_GSUTIL)
[email protected]605d81d2015-09-18 22:33:53141 parser.add_argument('--target', default=bin_dir,
142 help='The target directory to download/store a gsutil version in. '
143 '(default is %(default)s).')
[email protected]7a790542014-12-10 02:04:39144 parser.add_argument('args', nargs=argparse.REMAINDER)
145
[email protected]c13b0542014-12-18 01:06:20146 args, extras = parser.parse_known_args()
147 if args.args and args.args[0] == '--':
148 args.args.pop(0)
149 if extras:
150 args.args = extras + args.args
[email protected]605d81d2015-09-18 22:33:53151 return args
[email protected]7a790542014-12-10 02:04:39152
153
154def main():
[email protected]605d81d2015-09-18 22:33:53155 args = parse_args()
156 return run_gsutil(args.force_version, args.fallback, args.target, args.args,
157 clean=args.clean)
[email protected]7a790542014-12-10 02:04:39158
159if __name__ == '__main__':
160 sys.exit(main())