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.
27 GS_PATTERN = 'gs://%s'
28 GS_HTTPS_PATTERN = 'https://storage.googleapis.com/%s'
31 def LegalizeName(name):
32 """ Return a file name suitable for uploading to Google Storage.
34 The names of such files cannot contain dashes or other non-identifier
37 return re.sub(r'[^A-Za-z0-9_/.]', '_', name)
40 def HttpDownload(url, target):
41 """Default download route."""
42 http_download.HttpDownload(url, os.path.abspath(target), verbose=False,
46 class GSDStorageError(Exception):
47 """Error indicating writing to storage failed."""
51 class GSDStorage(object):
52 """A wrapper for reading and writing to GSD buckets.
54 Multiple read buckets may be specified, and the wrapper will sequentially try
55 each and fall back to the next if the previous fails.
56 Writing is to a single bucket.
63 download=HttpDownload):
64 """Init for this class.
67 write_bucket: Google storage location to write to.
68 read_buckets: Google storage locations to read from in preferred order.
69 gsutil: List of cmd components needed to call gsutil.
70 call: Testing hook to intercept command invocation.
71 download: Testing hook to intercept download.
75 # Require that gsutil be Python if it is specified in the environment.
76 gsutil = [sys.executable,
77 file_tools.Which(os.environ.get('GSUTIL', 'gsutil'),
78 require_executable=False)]
79 except file_tools.ExecutableNotFound:
81 assert isinstance(gsutil, list)
82 assert isinstance(read_buckets, list)
84 self._write_bucket = write_bucket
85 self._read_buckets = read_buckets
87 self._download = download
89 def PutFile(self, path, key, clobber=True):
90 """Write a file to global storage.
93 path: Path of the file to write.
94 key: Key to store file under.
96 GSDStorageError if the underlying storage fails.
100 if self._write_bucket is None:
101 raise GSDStorageError('no bucket when storing %s to %s' % (path, key))
102 obj = self._write_bucket + '/' + key
103 arguments = ['cp', '-a', 'public-read']
105 arguments.append('-n')
107 # Using file://c:/foo/bar form of path as gsutil does not like drive
108 # letters without it.
109 cmd = self._gsutil + arguments + [
110 'file://' + os.path.abspath(path).replace(os.sep, '/'),
112 logging.info('Running: %s' % str(cmd))
113 if self._call(cmd) != 0:
114 raise GSDStorageError('failed when storing %s to %s (%s)' % (
116 return GS_HTTPS_PATTERN % obj
118 def PutData(self, data, key, clobber=True):
119 """Write data to global storage.
123 key: Key to store file under.
125 GSDStorageError if the underlying storage fails.
129 handle, path = tempfile.mkstemp(prefix='gdstore', suffix='.tmp')
132 file_tools.WriteFile(data, path)
133 return self.PutFile(path, key, clobber=clobber)
137 def GetFile(self, key, path):
138 """Read a file from global storage.
141 key: Key to store file under.
142 path: Destination filename.
144 URL used on success or None for failure.
146 for bucket in self._read_buckets:
148 obj = bucket + '/' + key
149 uri = GS_HTTPS_PATTERN % obj
150 logging.debug('Downloading: %s to %s' % (uri, path))
151 self._download(uri, path)
154 logging.debug('Failed downloading: %s to %s' % (uri, path))
157 def GetSecureFile(self, key, path):
158 """ Read a non-publicly-accessible file from global storage.
161 key: Key file is stored under.
162 path: Destination filename
164 command used on success or None on failure.
166 for bucket in self._read_buckets:
168 obj = bucket + '/' + key
169 cmd = self._gsutil + [
170 'cp', GS_PATTERN % obj,
171 'file://' + os.path.abspath(path).replace(os.sep, '/')]
172 logging.info('Running: %s' % str(cmd))
173 if self._call(cmd) == 0:
176 logging.debug('Failed to fetch %s from %s (%s)' % (key, path, cmd))
179 def GetData(self, key):
180 """Read data from global storage.
183 key: Key to store file under.
185 Data from storage, or None for failure.
187 work_dir = tempfile.mkdtemp(prefix='gdstore', suffix='.tmp')
189 path = os.path.join(work_dir, 'data')
190 if self.GetFile(key, path) is not None:
191 return file_tools.ReadFile(path)
194 shutil.rmtree(work_dir)