PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/telemetry/third_party/gsutilz/gslib/storage_url.py

https://gitlab.com/jonnialva90/iridium-browser
Python | 324 lines | 245 code | 30 blank | 49 comment | 11 complexity | 9a445995f19e0a77f454aa4f85c2c7d9 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2013 Google Inc. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """File and Cloud URL representation classes."""
  16. from __future__ import absolute_import
  17. import os
  18. import re
  19. from gslib.exception import InvalidUrlError
  20. # Matches provider strings of the form 'gs://'
  21. PROVIDER_REGEX = re.compile(r'(?P<provider>[^:]*)://$')
  22. # Matches bucket strings of the form 'gs://bucket'
  23. BUCKET_REGEX = re.compile(r'(?P<provider>[^:]*)://(?P<bucket>[^/]*)/{0,1}$')
  24. # Matches object strings of the form 'gs://bucket/obj'
  25. OBJECT_REGEX = re.compile(
  26. r'(?P<provider>[^:]*)://(?P<bucket>[^/]*)/(?P<object>.*)')
  27. # Matches versioned object strings of the form 'gs://bucket/obj#1234'
  28. GS_GENERATION_REGEX = re.compile(r'(?P<object>.+)#(?P<generation>[0-9]+)$')
  29. # Matches versioned object strings of the form 's3://bucket/obj#NULL'
  30. S3_VERSION_REGEX = re.compile(r'(?P<object>.+)#(?P<version_id>.+)$')
  31. # Matches file strings of the form 'file://dir/filename'
  32. FILE_OBJECT_REGEX = re.compile(r'([^:]*://)(?P<filepath>.*)')
  33. # Regex to disallow buckets violating charset or not [3..255] chars total.
  34. BUCKET_NAME_RE = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9\._-]{1,253}[a-zA-Z0-9]$')
  35. # Regex to disallow buckets with individual DNS labels longer than 63.
  36. TOO_LONG_DNS_NAME_COMP = re.compile(r'[-_a-z0-9]{64}')
  37. # Regex to determine if a string contains any wildcards.
  38. WILDCARD_REGEX = re.compile(r'[*?\[\]]')
  39. class StorageUrl(object):
  40. """Abstract base class for file and Cloud Storage URLs."""
  41. def Clone(self):
  42. raise NotImplementedError('Clone not overridden')
  43. def IsFileUrl(self):
  44. raise NotImplementedError('IsFileUrl not overridden')
  45. def IsCloudUrl(self):
  46. raise NotImplementedError('IsCloudUrl not overridden')
  47. def IsStream(self):
  48. raise NotImplementedError('IsStream not overridden')
  49. def CreatePrefixUrl(self, wildcard_suffix=None):
  50. """Returns a prefix of this URL that can be used for iterating.
  51. Args:
  52. wildcard_suffix: If supplied, this wildcard suffix will be appended to the
  53. prefix with a trailing slash before being returned.
  54. Returns:
  55. A prefix of this URL that can be used for iterating.
  56. If this URL contains a trailing slash, it will be stripped to create the
  57. prefix. This helps avoid infinite looping when prefixes are iterated, but
  58. preserves other slashes so that objects with '/' in the name are handled
  59. properly.
  60. For example, when recursively listing a bucket with the following contents:
  61. gs://bucket// <-- object named slash
  62. gs://bucket//one-dir-deep
  63. a top-level expansion with '/' as a delimiter will result in the following
  64. URL strings:
  65. 'gs://bucket//' : OBJECT
  66. 'gs://bucket//' : PREFIX
  67. If we right-strip all slashes from the prefix entry and add a wildcard
  68. suffix, we will get 'gs://bucket/*' which will produce identical results
  69. (and infinitely recurse).
  70. Example return values:
  71. ('gs://bucket/subdir/', '*') becomes 'gs://bucket/subdir/*'
  72. ('gs://bucket/', '*') becomes 'gs://bucket/*'
  73. ('gs://bucket/', None) becomes 'gs://bucket'
  74. ('gs://bucket/subdir//', '*') becomes 'gs://bucket/subdir//*'
  75. ('gs://bucket/subdir///', '**') becomes 'gs://bucket/subdir///**'
  76. ('gs://bucket/subdir/', '*') where 'subdir/' is an object becomes
  77. 'gs://bucket/subdir/*', but iterating on this will return 'subdir/'
  78. as a BucketListingObject, so we will not recurse on it as a subdir
  79. during listing.
  80. """
  81. raise NotImplementedError('CreatePrefixUrl not overridden')
  82. @property
  83. def url_string(self):
  84. raise NotImplementedError('url_string not overridden')
  85. @property
  86. def versionless_url_string(self):
  87. raise NotImplementedError('versionless_url_string not overridden')
  88. def __eq__(self, other):
  89. return self.url_string == other.url_string
  90. def __hash__(self):
  91. return hash(self.url_string)
  92. class _FileUrl(StorageUrl):
  93. """File URL class providing parsing and convenience methods.
  94. This class assists with usage and manipulation of an
  95. (optionally wildcarded) file URL string. Depending on the string
  96. contents, this class represents one or more directories or files.
  97. For File URLs, scheme is always file, bucket_name is always blank,
  98. and object_name contains the file/directory path.
  99. """
  100. def __init__(self, url_string, is_stream=False):
  101. self.scheme = 'file'
  102. self.bucket_name = ''
  103. match = FILE_OBJECT_REGEX.match(url_string)
  104. if match and match.lastindex == 2:
  105. self.object_name = match.group(2)
  106. else:
  107. self.object_name = url_string
  108. self.generation = None
  109. self.is_stream = is_stream
  110. self.delim = os.sep
  111. def Clone(self):
  112. return _FileUrl(self.url_string)
  113. def IsFileUrl(self):
  114. return True
  115. def IsCloudUrl(self):
  116. return False
  117. def IsStream(self):
  118. return self.is_stream
  119. def IsDirectory(self):
  120. return not self.IsStream() and os.path.isdir(self.object_name)
  121. def CreatePrefixUrl(self, wildcard_suffix=None):
  122. return self.url_string
  123. @property
  124. def url_string(self):
  125. return '%s://%s' % (self.scheme, self.object_name)
  126. @property
  127. def versionless_url_string(self):
  128. return self.url_string
  129. def __str__(self):
  130. return self.url_string
  131. class _CloudUrl(StorageUrl):
  132. """Cloud URL class providing parsing and convenience methods.
  133. This class assists with usage and manipulation of an
  134. (optionally wildcarded) cloud URL string. Depending on the string
  135. contents, this class represents a provider, bucket(s), or object(s).
  136. This class operates only on strings. No cloud storage API calls are
  137. made from this class.
  138. """
  139. def __init__(self, url_string):
  140. self.scheme = None
  141. self.bucket_name = None
  142. self.object_name = None
  143. self.generation = None
  144. self.delim = '/'
  145. provider_match = PROVIDER_REGEX.match(url_string)
  146. bucket_match = BUCKET_REGEX.match(url_string)
  147. if provider_match:
  148. self.scheme = provider_match.group('provider')
  149. elif bucket_match:
  150. self.scheme = bucket_match.group('provider')
  151. self.bucket_name = bucket_match.group('bucket')
  152. if (not ContainsWildcard(self.bucket_name) and
  153. (not BUCKET_NAME_RE.match(self.bucket_name) or
  154. TOO_LONG_DNS_NAME_COMP.search(self.bucket_name))):
  155. raise InvalidUrlError('Invalid bucket name in URL "%s"' % url_string)
  156. else:
  157. object_match = OBJECT_REGEX.match(url_string)
  158. if object_match:
  159. self.scheme = object_match.group('provider')
  160. self.bucket_name = object_match.group('bucket')
  161. self.object_name = object_match.group('object')
  162. if self.scheme == 'gs':
  163. generation_match = GS_GENERATION_REGEX.match(self.object_name)
  164. if generation_match:
  165. self.object_name = generation_match.group('object')
  166. self.generation = generation_match.group('generation')
  167. elif self.scheme == 's3':
  168. version_match = S3_VERSION_REGEX.match(self.object_name)
  169. if version_match:
  170. self.object_name = version_match.group('object')
  171. self.generation = version_match.group('version_id')
  172. else:
  173. raise InvalidUrlError(
  174. 'CloudUrl: URL string %s did not match URL regex' % url_string)
  175. def Clone(self):
  176. return _CloudUrl(self.url_string)
  177. def IsFileUrl(self):
  178. return False
  179. def IsCloudUrl(self):
  180. return True
  181. def IsStream(self):
  182. raise NotImplementedError('IsStream not supported on CloudUrl')
  183. def IsBucket(self):
  184. return bool(self.bucket_name and not self.object_name)
  185. def IsObject(self):
  186. return bool(self.bucket_name and self.object_name)
  187. def HasGeneration(self):
  188. return bool(self.generation)
  189. def IsProvider(self):
  190. return bool(self.scheme and not self.bucket_name)
  191. def CreatePrefixUrl(self, wildcard_suffix=None):
  192. prefix = StripOneSlash(self.versionless_url_string)
  193. if wildcard_suffix:
  194. prefix = '%s/%s' % (prefix, wildcard_suffix)
  195. return prefix
  196. @property
  197. def bucket_url_string(self):
  198. return '%s://%s/' % (self.scheme, self.bucket_name)
  199. @property
  200. def url_string(self):
  201. url_str = self.versionless_url_string
  202. if self.HasGeneration():
  203. url_str += '#%s' % self.generation
  204. return url_str
  205. @property
  206. def versionless_url_string(self):
  207. if self.IsProvider():
  208. return '%s://' % self.scheme
  209. elif self.IsBucket():
  210. return self.bucket_url_string
  211. return '%s://%s/%s' % (self.scheme, self.bucket_name, self.object_name)
  212. def __str__(self):
  213. return self.url_string
  214. def _GetSchemeFromUrlString(url_str):
  215. """Returns scheme component of a URL string."""
  216. end_scheme_idx = url_str.find('://')
  217. if end_scheme_idx == -1:
  218. # File is the default scheme.
  219. return 'file'
  220. else:
  221. return url_str[0:end_scheme_idx].lower()
  222. def _GetPathFromUrlString(url_str):
  223. """Returns path component of a URL string."""
  224. end_scheme_idx = url_str.find('://')
  225. if end_scheme_idx == -1:
  226. return url_str
  227. else:
  228. return url_str[end_scheme_idx + 3:]
  229. def IsFileUrlString(url_str):
  230. """Returns whether a string is a file URL."""
  231. return _GetSchemeFromUrlString(url_str) == 'file'
  232. def StorageUrlFromString(url_str):
  233. """Static factory function for creating a StorageUrl from a string."""
  234. scheme = _GetSchemeFromUrlString(url_str)
  235. if scheme not in ('file', 's3', 'gs'):
  236. raise InvalidUrlError('Unrecognized scheme "%s"' % scheme)
  237. if scheme == 'file':
  238. path = _GetPathFromUrlString(url_str)
  239. is_stream = (path == '-')
  240. return _FileUrl(url_str, is_stream=is_stream)
  241. return _CloudUrl(url_str)
  242. def StripOneSlash(url_str):
  243. if url_str and url_str.endswith('/'):
  244. return url_str[:-1]
  245. return url_str
  246. def ContainsWildcard(url_string):
  247. """Checks whether url_string contains a wildcard.
  248. Args:
  249. url_string: URL string to check.
  250. Returns:
  251. bool indicator.
  252. """
  253. return bool(WILDCARD_REGEX.search(url_string))