OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 from third_party.cloudstorage import cloudstorage_api | 5 from third_party.cloudstorage import cloudstorage_api |
6 from third_party.cloudstorage import common | 6 from third_party.cloudstorage import common |
7 from third_party.cloudstorage import errors | 7 from third_party.cloudstorage import errors |
8 | 8 |
9 from docs_server_utils import StringIdentity | 9 from docs_server_utils import StringIdentity |
10 from file_system import FileSystem, FileNotFoundError, StatInfo | 10 from file_system import FileSystem, FileNotFoundError, StatInfo |
11 from future import Gettable, Future | 11 from future import Gettable, Future |
| 12 from path_util import ( |
| 13 AssertIsDirectory, AssertIsFile, AssertIsValid, IsDirectory, Join) |
12 | 14 |
13 import logging | 15 import logging |
14 import traceback | 16 import traceback |
15 | 17 |
| 18 |
| 19 # See gcs_file_system_provider.py for documentation on using Google Cloud |
| 20 # Storage as a filesystem. |
| 21 # |
| 22 # Note that the path requirements for GCS are different for the docserver; |
| 23 # GCS requires that paths start with a /, we require that they don't. |
| 24 |
| 25 |
16 # Name of the file containing the Git hash of the latest commit sync'ed | 26 # Name of the file containing the Git hash of the latest commit sync'ed |
17 # to Cloud Storage. This file is generated by the Github->GCS sync script | 27 # to Cloud Storage. This file is generated by the Github->GCS sync script |
18 LAST_COMMIT_HASH_FILENAME='.__lastcommit.txt' | 28 LAST_COMMIT_HASH_FILENAME = '.__lastcommit.txt' |
19 | 29 |
20 '''See gcs_file_system_provider.py for documentation on using Google Cloud | |
21 Storage as a filesystem. | |
22 ''' | |
23 def _ReadFile(filename): | 30 def _ReadFile(filename): |
| 31 AssertIsFile(filename) |
24 try: | 32 try: |
25 with cloudstorage_api.open(filename, 'r') as f: | 33 with cloudstorage_api.open('/' + filename, 'r') as f: |
26 return f.read() | 34 return f.read() |
27 except errors.Error: | 35 except errors.Error: |
28 raise FileNotFoundError('Read failed for %s: %s' % (filename, | 36 raise FileNotFoundError('Read failed for %s: %s' % (filename, |
29 traceback.format_exc())) | 37 traceback.format_exc())) |
30 | 38 |
31 def _ListDir(dir_name): | 39 def _ListDir(dir_name): |
| 40 AssertIsDirectory(dir_name) |
32 try: | 41 try: |
33 files = cloudstorage_api.listbucket(dir_name) | 42 files = cloudstorage_api.listbucket('/' + dir_name) |
34 return [os_path.filename for os_path in files] | 43 return [os_path.filename.lstrip('/') for os_path in files] |
35 except errors.Error: | 44 except errors.Error: |
36 raise FileNotFoundError('cloudstorage.listbucket failed for %s: %s' % | 45 raise FileNotFoundError('cloudstorage.listbucket failed for %s: %s' % |
37 (dir_name, traceback.format_exc())) | 46 (dir_name, traceback.format_exc())) |
38 | 47 |
39 def _CreateStatInfo(bucket, path): | 48 def _CreateStatInfo(bucket, path): |
40 bucket = '/%s' % bucket | 49 full_path = Join(bucket, path) |
41 full_path = '/'.join( (bucket, path.lstrip('/')) ) | 50 last_commit_file = Join(bucket, LAST_COMMIT_HASH_FILENAME) |
42 last_commit_file = '%s/%s' % (bucket, LAST_COMMIT_HASH_FILENAME) | |
43 try: | 51 try: |
44 last_commit = _ReadFile(last_commit_file) | 52 last_commit = _ReadFile(last_commit_file) |
45 if full_path.endswith('/'): | 53 if IsDirectory(full_path): |
46 child_versions = dict() | 54 child_versions = dict() |
47 # Fetching stats for all files under full_path, recursively. The | 55 # Fetching stats for all files under full_path, recursively. The |
48 # listbucket method uses a prefix approach to simulate hierarchy, | 56 # listbucket method uses a prefix approach to simulate hierarchy, |
49 # but calling it without the "delimiter" argument searches for prefix, | 57 # but calling it without the "delimiter" argument searches for prefix, |
50 # which means, for directories, everything beneath it. | 58 # which means, for directories, everything beneath it. |
51 for _file in cloudstorage_api.listbucket(full_path): | 59 for _file in cloudstorage_api.listbucket('/' + full_path): |
52 filename = _file.filename[len(full_path):] | 60 filename = _file.filename.lstrip('/')[len(full_path):] |
53 child_versions[filename] = last_commit | 61 child_versions[filename] = last_commit |
54 else: | 62 else: |
55 child_versions = None | 63 child_versions = None |
56 return StatInfo(last_commit, child_versions) | 64 return StatInfo(last_commit, child_versions) |
57 except (TypeError, errors.Error): | 65 except (TypeError, errors.Error): |
58 raise FileNotFoundError('cloudstorage.stat failed for %s: %s' % (path, | 66 raise FileNotFoundError('cloudstorage.stat failed for %s: %s' % (path, |
59 traceback.format_exc())) | 67 traceback.format_exc())) |
60 | 68 |
61 class CloudStorageFileSystem(FileSystem): | 69 class CloudStorageFileSystem(FileSystem): |
62 '''FileSystem implementation which fetches resources from Google Cloud | 70 '''FileSystem implementation which fetches resources from Google Cloud |
63 Storage. | 71 Storage. |
64 ''' | 72 ''' |
65 def __init__(self, bucket, debug_access_token=None, debug_bucket_prefix=None): | 73 def __init__(self, bucket, debug_access_token=None, debug_bucket_prefix=None): |
66 self._bucket = bucket | 74 self._bucket = bucket |
67 if debug_access_token: | 75 if debug_access_token: |
68 logging.debug('gcs: using debug access token: %s' % debug_access_token) | 76 logging.debug('gcs: using debug access token: %s' % debug_access_token) |
69 common.set_access_token(debug_access_token) | 77 common.set_access_token(debug_access_token) |
70 if debug_bucket_prefix: | 78 if debug_bucket_prefix: |
71 logging.debug('gcs: prefixing all bucket names with %s' % | 79 logging.debug('gcs: prefixing all bucket names with %s' % |
72 debug_bucket_prefix) | 80 debug_bucket_prefix) |
73 self._bucket = debug_bucket_prefix + self._bucket | 81 self._bucket = debug_bucket_prefix + self._bucket |
| 82 AssertIsValid(self._bucket) |
74 | 83 |
75 def Read(self, paths): | 84 def Read(self, paths): |
76 def resolve(): | 85 def resolve(): |
77 try: | 86 try: |
78 result = {} | 87 result = {} |
79 for path in paths: | 88 for path in paths: |
80 full_path = '/%s/%s' % (self._bucket, path.lstrip('/')) | 89 full_path = Join(self._bucket, path) |
81 logging.debug('gcs: requested path %s, reading %s' % | 90 logging.debug('gcs: requested path "%s", reading "%s"' % |
82 (path, full_path)) | 91 (path, full_path)) |
83 if path == '' or path.endswith('/'): | 92 if IsDirectory(path): |
84 result[path] = _ListDir(full_path) | 93 result[path] = _ListDir(full_path) |
85 else: | 94 else: |
86 result[path] = _ReadFile(full_path) | 95 result[path] = _ReadFile(full_path) |
87 return result | 96 return result |
88 except errors.AuthorizationError: | 97 except errors.AuthorizationError: |
89 self._warnAboutAuthError() | 98 self._warnAboutAuthError() |
90 raise | 99 raise |
91 | 100 |
92 return Future(delegate=Gettable(resolve)) | 101 return Future(delegate=Gettable(resolve)) |
93 | 102 |
94 def Refresh(self): | 103 def Refresh(self): |
95 return Future(value=()) | 104 return Future(value=()) |
96 | 105 |
97 def Stat(self, path): | 106 def Stat(self, path): |
| 107 AssertIsValid(path) |
98 try: | 108 try: |
99 return _CreateStatInfo(self._bucket, path) | 109 return _CreateStatInfo(self._bucket, path) |
100 except errors.AuthorizationError: | 110 except errors.AuthorizationError: |
101 self._warnAboutAuthError() | 111 self._warnAboutAuthError() |
102 raise | 112 raise |
103 | 113 |
104 def GetIdentity(self): | 114 def GetIdentity(self): |
105 return '@'.join((self.__class__.__name__, StringIdentity(self._bucket))) | 115 return '@'.join((self.__class__.__name__, StringIdentity(self._bucket))) |
106 | 116 |
107 def __repr__(self): | 117 def __repr__(self): |
108 return 'LocalFileSystem(%s)' % self._bucket | 118 return 'LocalFileSystem(%s)' % self._bucket |
109 | 119 |
110 def _warnAboutAuthError(self): | 120 def _warnAboutAuthError(self): |
111 logging.warn(('Authentication error on Cloud Storage. Check if your' | 121 logging.warn(('Authentication error on Cloud Storage. Check if your' |
112 ' appengine project has permissions to Read the GCS' | 122 ' appengine project has permissions to Read the GCS' |
113 ' buckets. If you are running a local appengine server,' | 123 ' buckets. If you are running a local appengine server,' |
114 ' you need to set an access_token in' | 124 ' you need to set an access_token in' |
115 ' local_debug/gcs_debug.conf.' | 125 ' local_debug/gcs_debug.conf.' |
116 ' Remember that this token expires in less than 10' | 126 ' Remember that this token expires in less than 10' |
117 ' minutes, so keep it updated. See' | 127 ' minutes, so keep it updated. See' |
118 ' gcs_file_system_provider.py for instructions.')); | 128 ' gcs_file_system_provider.py for instructions.')); |
119 logging.debug(traceback.format_exc()) | 129 logging.debug(traceback.format_exc()) |
OLD | NEW |