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)
39 def HttpDownload(url, target):
40 """Default download route."""
41 http_download.HttpDownload(url, os.path.abspath(target),
45 class GSDStorageError(Exception):
46 """Error indicating writing to storage failed."""
50 class GSDStorage(object):
51 """A wrapper for reading and writing to GSD buckets.
53 Multiple read buckets may be specified, and the wrapper will sequentially try
54 each and fall back to the next if the previous fails.
55 Writing is to a single bucket.
62 download=HttpDownload):
63 """Init for this class.
66 write_bucket: Google storage location to write to.
67 read_buckets: Google storage locations to read from in preferred order.
68 gsutil: List of cmd components needed to call gsutil.
69 call: Testing hook to intercept command invocation.
70 download: Testing hook to intercept download.
74 # Require that gsutil be Python if it is specified in the environment.
75 gsutil = [sys.executable,
76 file_tools.Which(os.environ.get('GSUTIL', 'gsutil'),
77 require_executable=False)]
78 except file_tools.ExecutableNotFound:
80 assert isinstance(gsutil, list)
81 assert isinstance(read_buckets, list)
83 self._write_bucket = write_bucket
84 self._read_buckets = read_buckets
86 self._download = download
88 def PutFile(self, path, key):
89 """Write a file to global storage.
92 path: Path of the file to write.
93 key: Key to store file under.
95 GSDStorageError if the underlying storage fails.
99 if self._write_bucket is None:
100 raise GSDStorageError('no bucket when storing %s to %s' % (path, key))
101 obj = self._write_bucket + '/' + key
102 # Using file://c:/foo/bar form of path as gsutil does not like drive
103 # letters without it.
104 cmd = self._gsutil + [
105 'cp', '-a', 'public-read',
106 'file://' + os.path.abspath(path).replace(os.sep, '/'),
108 logging.info('Running: %s' % str(cmd))
109 if self._call(cmd) != 0:
110 raise GSDStorageError('failed when storing %s to %s (%s)' % (
112 return GS_HTTPS_PATTERN % obj
114 def PutData(self, data, key):
115 """Write data to global storage.
119 key: Key to store file under.
121 GSDStorageError if the underlying storage fails.
125 handle, path = tempfile.mkstemp(prefix='gdstore', suffix='.tmp')
128 file_tools.WriteFile(data, path)
129 return self.PutFile(path, key)
133 def GetFile(self, key, path):
134 """Read a file from global storage.
137 key: Key to store file under.
138 path: Destination filename.
140 URL used on success or None for failure.
142 for bucket in self._read_buckets:
144 obj = bucket + '/' + key
145 uri = GS_HTTPS_PATTERN % obj
146 logging.debug('Downloading: %s to %s' % (uri, path))
147 self._download(uri, path)
150 logging.debug('Failed downloading: %s to %s' % (uri, path))
153 def GetSecureFile(self, key, path):
154 """ Read a non-publicly-accessible file from global storage.
157 key: Key file is stored under.
158 path: Destination filename
160 command used on success or None on failure.
162 for bucket in self._read_buckets:
164 obj = bucket + '/' + key
165 cmd = self._gsutil + [
166 'cp', GS_PATTERN % obj,
167 'file://' + os.path.abspath(path).replace(os.sep, '/')]
168 logging.info('Running: %s' % str(cmd))
169 if self._call(cmd) == 0:
172 logging.debug('Failed to fetch %s from %s (%s)' % (key, path, cmd))
175 def GetData(self, key):
176 """Read data from global storage.
179 key: Key to store file under.
181 Data from storage, or None for failure.
183 work_dir = tempfile.mkdtemp(prefix='gdstore', suffix='.tmp')
185 path = os.path.join(work_dir, 'data')
186 if self.GetFile(key, path) is not None:
187 return file_tools.ReadFile(path)
190 shutil.rmtree(work_dir)