2 # Copyright (c) 2012 The Native Client 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.
6 """Provide Google Storage access.
8 Provide an high-level interface to Google Storage.
9 Operations are provided to read/write whole files and to
10 read/write strings. This allows google storage to be treated
11 more or less like a key+value data-store.
29 GS_PATTERN = 'gs://%s'
30 GS_HTTPS_PATTERN = 'https://storage.googleapis.com/%s'
33 def LegalizeName(name):
34 """ Return a file name suitable for uploading to Google Storage.
36 The names of such files cannot contain dashes or other non-identifier
39 return re.sub(r'[^A-Za-z0-9_/.]', '_', name)
42 def HttpDownload(url, target):
43 """Default download route."""
44 http_download.HttpDownload(url, os.path.abspath(target), verbose=False,
48 class GSDStorageError(Exception):
49 """Error indicating writing to storage failed."""
53 class GSDStorage(object):
54 """A wrapper for reading and writing to GSD buckets.
56 Multiple read buckets may be specified, and the wrapper will sequentially try
57 each and fall back to the next if the previous fails.
58 Writing is to a single bucket.
65 download=HttpDownload):
66 """Init for this class.
69 write_bucket: Google storage location to write to.
70 read_buckets: Google storage locations to read from in preferred order.
71 gsutil: List of cmd components needed to call gsutil.
72 call: Testing hook to intercept command invocation.
73 download: Testing hook to intercept download.
76 gsutil_script = platform.CygPath(os.environ.get('GSUTIL', 'gsutil'))
78 # Require that gsutil be Python if it is specified in the environment.
79 gsutil = [sys.executable,
80 file_tools.Which(gsutil_script,
81 require_executable=False)]
82 except file_tools.ExecutableNotFound:
83 gsutil = [gsutil_script]
84 assert isinstance(gsutil, list)
85 assert isinstance(read_buckets, list)
87 self._write_bucket = write_bucket
88 self._read_buckets = read_buckets
90 self._download = download
92 def Exists(self, key):
93 """Queries whether or not a key exists.
96 key: Key file is stored under.
98 URL of existing key, or False if file does not exist.
100 for bucket in set(self._read_buckets + [self._write_bucket]):
101 obj = posixpath.join(bucket, key)
102 cmd = self._gsutil + ['ls', GS_PATTERN % obj]
103 logging.info('Running: %s', str(cmd))
104 if self._call(cmd) == 0:
105 return GS_HTTPS_PATTERN % obj
109 def PutFile(self, path, key, clobber=True):
110 """Write a file to global storage.
113 path: Path of the file to write.
114 key: Key to store file under.
116 GSDStorageError if the underlying storage fails.
120 if self._write_bucket is None:
121 raise GSDStorageError('no bucket when storing %s to %s' % (path, key))
122 obj = self._write_bucket + '/' + key
123 arguments = ['cp', '-a', 'public-read']
125 arguments.append('-n')
127 # Using file://c:/foo/bar form of path as gsutil does not like drive
128 # letters without it.
129 cmd = self._gsutil + arguments + [
130 'file://' + os.path.abspath(path).replace(os.sep, '/'),
132 logging.info('Running: %s' % str(cmd))
133 if self._call(cmd) != 0:
134 raise GSDStorageError('failed when storing %s to %s (%s)' % (
136 return GS_HTTPS_PATTERN % obj
138 def PutData(self, data, key, clobber=True):
139 """Write data to global storage.
143 key: Key to store file under.
145 GSDStorageError if the underlying storage fails.
149 handle, path = tempfile.mkstemp(prefix='gdstore', suffix='.tmp')
152 file_tools.WriteFile(data, path)
153 return self.PutFile(path, key, clobber=clobber)
157 def GetFile(self, key, path):
158 """Read a file from global storage.
161 key: Key to store file under.
162 path: Destination filename.
164 URL used on success or None for failure.
166 for bucket in self._read_buckets:
168 obj = bucket + '/' + key
169 uri = GS_HTTPS_PATTERN % obj
170 logging.debug('Downloading: %s to %s' % (uri, path))
171 self._download(uri, path)
174 logging.debug('Failed downloading: %s to %s' % (uri, path))
177 def GetSecureFile(self, key, path):
178 """ Read a non-publicly-accessible file from global storage.
181 key: Key file is stored under.
182 path: Destination filename
184 command used on success or None on failure.
186 for bucket in self._read_buckets:
188 obj = bucket + '/' + key
189 cmd = self._gsutil + [
190 'cp', GS_PATTERN % obj,
191 'file://' + os.path.abspath(path).replace(os.sep, '/')]
192 logging.info('Running: %s' % str(cmd))
193 if self._call(cmd) == 0:
196 logging.debug('Failed to fetch %s from %s (%s)' % (key, path, cmd))
199 def GetData(self, key):
200 """Read data from global storage.
203 key: Key to store file under.
205 Data from storage, or None for failure.
207 work_dir = tempfile.mkdtemp(prefix='gdstore', suffix='.tmp')
209 path = os.path.join(work_dir, 'data')
210 if self.GetFile(key, path) is not None:
211 return file_tools.ReadFile(path)
214 shutil.rmtree(work_dir)