storage_uri.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. # Copyright 2010 Google Inc.
  2. # Copyright (c) 2011, Nexenta Systems Inc.
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining a
  5. # copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish, dis-
  8. # tribute, sublicense, and/or sell copies of the Software, and to permit
  9. # persons to whom the Software is furnished to do so, subject to the fol-
  10. # lowing conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included
  13. # in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  16. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  17. # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  18. # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  19. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21. # IN THE SOFTWARE.
  22. import boto
  23. import os
  24. import sys
  25. import textwrap
  26. from boto.s3.deletemarker import DeleteMarker
  27. from boto.exception import BotoClientError
  28. from boto.exception import InvalidUriError
  29. class StorageUri(object):
  30. """
  31. Base class for representing storage provider-independent bucket and
  32. object name with a shorthand URI-like syntax.
  33. This is an abstract class: the constructor cannot be called (throws an
  34. exception if you try).
  35. """
  36. connection = None
  37. # Optional args that can be set from one of the concrete subclass
  38. # constructors, to change connection behavior (e.g., to override
  39. # https_connection_factory).
  40. connection_args = None
  41. # Map of provider scheme ('s3' or 'gs') to AWSAuthConnection object. We
  42. # maintain a pool here in addition to the connection pool implemented
  43. # in AWSAuthConnection because the latter re-creates its connection pool
  44. # every time that class is instantiated (so the current pool is used to
  45. # avoid re-instantiating AWSAuthConnection).
  46. provider_pool = {}
  47. def __init__(self):
  48. """Uncallable constructor on abstract base StorageUri class.
  49. """
  50. raise BotoClientError('Attempt to instantiate abstract StorageUri '
  51. 'class')
  52. def __repr__(self):
  53. """Returns string representation of URI."""
  54. return self.uri
  55. def equals(self, uri):
  56. """Returns true if two URIs are equal."""
  57. return self.uri == uri.uri
  58. def check_response(self, resp, level, uri):
  59. if resp is None:
  60. raise InvalidUriError('\n'.join(textwrap.wrap(
  61. 'Attempt to get %s for "%s" failed. This can happen if '
  62. 'the URI refers to a non-existent object or if you meant to '
  63. 'operate on a directory (e.g., leaving off -R option on gsutil '
  64. 'cp, mv, or ls of a bucket)' % (level, uri), 80)))
  65. def _check_bucket_uri(self, function_name):
  66. if issubclass(type(self), BucketStorageUri) and not self.bucket_name:
  67. raise InvalidUriError(
  68. '%s on bucket-less URI (%s)' % (function_name, self.uri))
  69. def _check_object_uri(self, function_name):
  70. if issubclass(type(self), BucketStorageUri) and not self.object_name:
  71. raise InvalidUriError('%s on object-less URI (%s)' %
  72. (function_name, self.uri))
  73. def _warn_about_args(self, function_name, **args):
  74. for arg in args:
  75. if args[arg]:
  76. sys.stderr.write(
  77. 'Warning: %s ignores argument: %s=%s\n' %
  78. (function_name, arg, str(args[arg])))
  79. def connect(self, access_key_id=None, secret_access_key=None, **kwargs):
  80. """
  81. Opens a connection to appropriate provider, depending on provider
  82. portion of URI. Requires Credentials defined in boto config file (see
  83. boto/pyami/config.py).
  84. @type storage_uri: StorageUri
  85. @param storage_uri: StorageUri specifying a bucket or a bucket+object
  86. @rtype: L{AWSAuthConnection<boto.gs.connection.AWSAuthConnection>}
  87. @return: A connection to storage service provider of the given URI.
  88. """
  89. connection_args = dict(self.connection_args or ())
  90. if (hasattr(self, 'suppress_consec_slashes') and
  91. 'suppress_consec_slashes' not in connection_args):
  92. connection_args['suppress_consec_slashes'] = (
  93. self.suppress_consec_slashes)
  94. connection_args.update(kwargs)
  95. if not self.connection:
  96. if self.scheme in self.provider_pool:
  97. self.connection = self.provider_pool[self.scheme]
  98. elif self.scheme == 's3':
  99. from boto.s3.connection import S3Connection
  100. self.connection = S3Connection(access_key_id,
  101. secret_access_key,
  102. **connection_args)
  103. self.provider_pool[self.scheme] = self.connection
  104. elif self.scheme == 'gs':
  105. from boto.gs.connection import GSConnection
  106. # Use OrdinaryCallingFormat instead of boto-default
  107. # SubdomainCallingFormat because the latter changes the hostname
  108. # that's checked during cert validation for HTTPS connections,
  109. # which will fail cert validation (when cert validation is
  110. # enabled).
  111. #
  112. # The same is not true for S3's HTTPS certificates. In fact,
  113. # we don't want to do this for S3 because S3 requires the
  114. # subdomain to match the location of the bucket. If the proper
  115. # subdomain is not used, the server will return a 301 redirect
  116. # with no Location header.
  117. #
  118. # Note: the following import can't be moved up to the
  119. # start of this file else it causes a config import failure when
  120. # run from the resumable upload/download tests.
  121. from boto.s3.connection import OrdinaryCallingFormat
  122. connection_args['calling_format'] = OrdinaryCallingFormat()
  123. self.connection = GSConnection(access_key_id,
  124. secret_access_key,
  125. **connection_args)
  126. self.provider_pool[self.scheme] = self.connection
  127. elif self.scheme == 'file':
  128. from boto.file.connection import FileConnection
  129. self.connection = FileConnection(self)
  130. else:
  131. raise InvalidUriError('Unrecognized scheme "%s"' %
  132. self.scheme)
  133. self.connection.debug = self.debug
  134. return self.connection
  135. def has_version(self):
  136. return (issubclass(type(self), BucketStorageUri)
  137. and ((self.version_id is not None)
  138. or (self.generation is not None)))
  139. def delete_key(self, validate=False, headers=None, version_id=None,
  140. mfa_token=None):
  141. self._check_object_uri('delete_key')
  142. bucket = self.get_bucket(validate, headers)
  143. return bucket.delete_key(self.object_name, headers, version_id,
  144. mfa_token)
  145. def list_bucket(self, prefix='', delimiter='', headers=None,
  146. all_versions=False):
  147. self._check_bucket_uri('list_bucket')
  148. bucket = self.get_bucket(headers=headers)
  149. if all_versions:
  150. return (v for v in bucket.list_versions(
  151. prefix=prefix, delimiter=delimiter, headers=headers)
  152. if not isinstance(v, DeleteMarker))
  153. else:
  154. return bucket.list(prefix=prefix, delimiter=delimiter,
  155. headers=headers)
  156. def get_all_keys(self, validate=False, headers=None, prefix=None):
  157. bucket = self.get_bucket(validate, headers)
  158. return bucket.get_all_keys(headers)
  159. def get_bucket(self, validate=False, headers=None):
  160. self._check_bucket_uri('get_bucket')
  161. conn = self.connect()
  162. bucket = conn.get_bucket(self.bucket_name, validate, headers)
  163. self.check_response(bucket, 'bucket', self.uri)
  164. return bucket
  165. def get_key(self, validate=False, headers=None, version_id=None):
  166. self._check_object_uri('get_key')
  167. bucket = self.get_bucket(validate, headers)
  168. key = bucket.get_key(self.object_name, headers, version_id)
  169. self.check_response(key, 'key', self.uri)
  170. return key
  171. def new_key(self, validate=False, headers=None):
  172. self._check_object_uri('new_key')
  173. bucket = self.get_bucket(validate, headers)
  174. return bucket.new_key(self.object_name)
  175. def get_contents_to_stream(self, fp, headers=None, version_id=None):
  176. self._check_object_uri('get_key')
  177. self._warn_about_args('get_key', validate=False)
  178. key = self.get_key(None, headers)
  179. self.check_response(key, 'key', self.uri)
  180. return key.get_contents_to_file(fp, headers, version_id=version_id)
  181. def get_contents_to_file(self, fp, headers=None, cb=None, num_cb=10,
  182. torrent=False, version_id=None,
  183. res_download_handler=None, response_headers=None,
  184. hash_algs=None):
  185. self._check_object_uri('get_contents_to_file')
  186. key = self.get_key(None, headers)
  187. self.check_response(key, 'key', self.uri)
  188. if hash_algs:
  189. key.get_contents_to_file(fp, headers, cb, num_cb, torrent,
  190. version_id, res_download_handler,
  191. response_headers,
  192. hash_algs=hash_algs)
  193. else:
  194. key.get_contents_to_file(fp, headers, cb, num_cb, torrent,
  195. version_id, res_download_handler,
  196. response_headers)
  197. def get_contents_as_string(self, validate=False, headers=None, cb=None,
  198. num_cb=10, torrent=False, version_id=None):
  199. self._check_object_uri('get_contents_as_string')
  200. key = self.get_key(validate, headers)
  201. self.check_response(key, 'key', self.uri)
  202. return key.get_contents_as_string(headers, cb, num_cb, torrent,
  203. version_id)
  204. def acl_class(self):
  205. conn = self.connect()
  206. acl_class = conn.provider.acl_class
  207. self.check_response(acl_class, 'acl_class', self.uri)
  208. return acl_class
  209. def canned_acls(self):
  210. conn = self.connect()
  211. canned_acls = conn.provider.canned_acls
  212. self.check_response(canned_acls, 'canned_acls', self.uri)
  213. return canned_acls
  214. class BucketStorageUri(StorageUri):
  215. """
  216. StorageUri subclass that handles bucket storage providers.
  217. Callers should instantiate this class by calling boto.storage_uri().
  218. """
  219. delim = '/'
  220. capabilities = set([]) # A set of additional capabilities.
  221. def __init__(self, scheme, bucket_name=None, object_name=None,
  222. debug=0, connection_args=None, suppress_consec_slashes=True,
  223. version_id=None, generation=None, is_latest=False):
  224. """Instantiate a BucketStorageUri from scheme,bucket,object tuple.
  225. @type scheme: string
  226. @param scheme: URI scheme naming the storage provider (gs, s3, etc.)
  227. @type bucket_name: string
  228. @param bucket_name: bucket name
  229. @type object_name: string
  230. @param object_name: object name, excluding generation/version.
  231. @type debug: int
  232. @param debug: debug level to pass in to connection (range 0..2)
  233. @type connection_args: map
  234. @param connection_args: optional map containing args to be
  235. passed to {S3,GS}Connection constructor (e.g., to override
  236. https_connection_factory).
  237. @param suppress_consec_slashes: If provided, controls whether
  238. consecutive slashes will be suppressed in key paths.
  239. @param version_id: Object version id (S3-specific).
  240. @param generation: Object generation number (GCS-specific).
  241. @param is_latest: boolean indicating that a versioned object is the
  242. current version
  243. After instantiation the components are available in the following
  244. fields: scheme, bucket_name, object_name, version_id, generation,
  245. is_latest, versionless_uri, version_specific_uri, uri.
  246. Note: If instantiated without version info, the string representation
  247. for a URI stays versionless; similarly, if instantiated with version
  248. info, the string representation for a URI stays version-specific. If you
  249. call one of the uri.set_contents_from_xyz() methods, a specific object
  250. version will be created, and its version-specific URI string can be
  251. retrieved from version_specific_uri even if the URI was instantiated
  252. without version info.
  253. """
  254. self.scheme = scheme
  255. self.bucket_name = bucket_name
  256. self.object_name = object_name
  257. self.debug = debug
  258. if connection_args:
  259. self.connection_args = connection_args
  260. self.suppress_consec_slashes = suppress_consec_slashes
  261. self.version_id = version_id
  262. self.generation = generation and int(generation)
  263. self.is_latest = is_latest
  264. self.is_version_specific = bool(self.generation) or bool(version_id)
  265. self._build_uri_strings()
  266. def _build_uri_strings(self):
  267. if self.bucket_name and self.object_name:
  268. self.versionless_uri = '%s://%s/%s' % (self.scheme, self.bucket_name,
  269. self.object_name)
  270. if self.generation:
  271. self.version_specific_uri = '%s#%s' % (self.versionless_uri,
  272. self.generation)
  273. elif self.version_id:
  274. self.version_specific_uri = '%s#%s' % (
  275. self.versionless_uri, self.version_id)
  276. if self.is_version_specific:
  277. self.uri = self.version_specific_uri
  278. else:
  279. self.uri = self.versionless_uri
  280. elif self.bucket_name:
  281. self.uri = ('%s://%s/' % (self.scheme, self.bucket_name))
  282. else:
  283. self.uri = ('%s://' % self.scheme)
  284. def _update_from_key(self, key):
  285. self._update_from_values(
  286. getattr(key, 'version_id', None),
  287. getattr(key, 'generation', None),
  288. getattr(key, 'is_latest', None),
  289. getattr(key, 'md5', None))
  290. def _update_from_values(self, version_id, generation, is_latest, md5):
  291. self.version_id = version_id
  292. self.generation = generation
  293. self.is_latest = is_latest
  294. self._build_uri_strings()
  295. self.md5 = md5
  296. def get_key(self, validate=False, headers=None, version_id=None):
  297. self._check_object_uri('get_key')
  298. bucket = self.get_bucket(validate, headers)
  299. if self.get_provider().name == 'aws':
  300. key = bucket.get_key(self.object_name, headers,
  301. version_id=(version_id or self.version_id))
  302. elif self.get_provider().name == 'google':
  303. key = bucket.get_key(self.object_name, headers,
  304. generation=self.generation)
  305. self.check_response(key, 'key', self.uri)
  306. return key
  307. def delete_key(self, validate=False, headers=None, version_id=None,
  308. mfa_token=None):
  309. self._check_object_uri('delete_key')
  310. bucket = self.get_bucket(validate, headers)
  311. if self.get_provider().name == 'aws':
  312. version_id = version_id or self.version_id
  313. return bucket.delete_key(self.object_name, headers, version_id,
  314. mfa_token)
  315. elif self.get_provider().name == 'google':
  316. return bucket.delete_key(self.object_name, headers,
  317. generation=self.generation)
  318. def clone_replace_name(self, new_name):
  319. """Instantiate a BucketStorageUri from the current BucketStorageUri,
  320. but replacing the object_name.
  321. @type new_name: string
  322. @param new_name: new object name
  323. """
  324. self._check_bucket_uri('clone_replace_name')
  325. return BucketStorageUri(
  326. self.scheme, bucket_name=self.bucket_name, object_name=new_name,
  327. debug=self.debug,
  328. suppress_consec_slashes=self.suppress_consec_slashes)
  329. def clone_replace_key(self, key):
  330. """Instantiate a BucketStorageUri from the current BucketStorageUri, by
  331. replacing the object name with the object name and other metadata found
  332. in the given Key object (including generation).
  333. @type key: Key
  334. @param key: key for the new StorageUri to represent
  335. """
  336. self._check_bucket_uri('clone_replace_key')
  337. version_id = None
  338. generation = None
  339. is_latest = False
  340. if hasattr(key, 'version_id'):
  341. version_id = key.version_id
  342. if hasattr(key, 'generation'):
  343. generation = key.generation
  344. if hasattr(key, 'is_latest'):
  345. is_latest = key.is_latest
  346. return BucketStorageUri(
  347. key.provider.get_provider_name(),
  348. bucket_name=key.bucket.name,
  349. object_name=key.name,
  350. debug=self.debug,
  351. suppress_consec_slashes=self.suppress_consec_slashes,
  352. version_id=version_id,
  353. generation=generation,
  354. is_latest=is_latest)
  355. def get_acl(self, validate=False, headers=None, version_id=None):
  356. """returns a bucket's acl"""
  357. self._check_bucket_uri('get_acl')
  358. bucket = self.get_bucket(validate, headers)
  359. # This works for both bucket- and object- level ACLs (former passes
  360. # key_name=None):
  361. key_name = self.object_name or ''
  362. if self.get_provider().name == 'aws':
  363. version_id = version_id or self.version_id
  364. acl = bucket.get_acl(key_name, headers, version_id)
  365. else:
  366. acl = bucket.get_acl(key_name, headers, generation=self.generation)
  367. self.check_response(acl, 'acl', self.uri)
  368. return acl
  369. def get_def_acl(self, validate=False, headers=None):
  370. """returns a bucket's default object acl"""
  371. self._check_bucket_uri('get_def_acl')
  372. bucket = self.get_bucket(validate, headers)
  373. acl = bucket.get_def_acl(headers)
  374. self.check_response(acl, 'acl', self.uri)
  375. return acl
  376. def get_cors(self, validate=False, headers=None):
  377. """returns a bucket's CORS XML"""
  378. self._check_bucket_uri('get_cors')
  379. bucket = self.get_bucket(validate, headers)
  380. cors = bucket.get_cors(headers)
  381. self.check_response(cors, 'cors', self.uri)
  382. return cors
  383. def set_cors(self, cors, validate=False, headers=None):
  384. """sets or updates a bucket's CORS XML"""
  385. self._check_bucket_uri('set_cors ')
  386. bucket = self.get_bucket(validate, headers)
  387. if self.scheme == 's3':
  388. bucket.set_cors(cors, headers)
  389. else:
  390. bucket.set_cors(cors.to_xml(), headers)
  391. def get_location(self, validate=False, headers=None):
  392. self._check_bucket_uri('get_location')
  393. bucket = self.get_bucket(validate, headers)
  394. return bucket.get_location(headers)
  395. def get_storage_class(self, validate=False, headers=None):
  396. self._check_bucket_uri('get_storage_class')
  397. # StorageClass is defined as a bucket and object param for GCS, but
  398. # only as a key param for S3.
  399. if self.scheme != 'gs':
  400. raise ValueError('get_storage_class() not supported for %s '
  401. 'URIs.' % self.scheme)
  402. bucket = self.get_bucket(validate, headers)
  403. return bucket.get_storage_class(headers)
  404. def set_storage_class(self, storage_class, validate=False, headers=None):
  405. """Updates a bucket's storage class."""
  406. self._check_bucket_uri('set_storage_class')
  407. # StorageClass is defined as a bucket and object param for GCS, but
  408. # only as a key param for S3.
  409. if self.scheme != 'gs':
  410. raise ValueError('set_storage_class() not supported for %s '
  411. 'URIs.' % self.scheme)
  412. bucket = self.get_bucket(validate, headers)
  413. bucket.set_storage_class(storage_class, headers)
  414. def get_subresource(self, subresource, validate=False, headers=None,
  415. version_id=None):
  416. self._check_bucket_uri('get_subresource')
  417. bucket = self.get_bucket(validate, headers)
  418. return bucket.get_subresource(subresource, self.object_name, headers,
  419. version_id)
  420. def add_group_email_grant(self, permission, email_address, recursive=False,
  421. validate=False, headers=None):
  422. self._check_bucket_uri('add_group_email_grant')
  423. if self.scheme != 'gs':
  424. raise ValueError('add_group_email_grant() not supported for %s '
  425. 'URIs.' % self.scheme)
  426. if self.object_name:
  427. if recursive:
  428. raise ValueError('add_group_email_grant() on key-ful URI cannot '
  429. 'specify recursive=True')
  430. key = self.get_key(validate, headers)
  431. self.check_response(key, 'key', self.uri)
  432. key.add_group_email_grant(permission, email_address, headers)
  433. elif self.bucket_name:
  434. bucket = self.get_bucket(validate, headers)
  435. bucket.add_group_email_grant(permission, email_address, recursive,
  436. headers)
  437. else:
  438. raise InvalidUriError('add_group_email_grant() on bucket-less URI '
  439. '%s' % self.uri)
  440. def add_email_grant(self, permission, email_address, recursive=False,
  441. validate=False, headers=None):
  442. self._check_bucket_uri('add_email_grant')
  443. if not self.object_name:
  444. bucket = self.get_bucket(validate, headers)
  445. bucket.add_email_grant(permission, email_address, recursive,
  446. headers)
  447. else:
  448. key = self.get_key(validate, headers)
  449. self.check_response(key, 'key', self.uri)
  450. key.add_email_grant(permission, email_address)
  451. def add_user_grant(self, permission, user_id, recursive=False,
  452. validate=False, headers=None):
  453. self._check_bucket_uri('add_user_grant')
  454. if not self.object_name:
  455. bucket = self.get_bucket(validate, headers)
  456. bucket.add_user_grant(permission, user_id, recursive, headers)
  457. else:
  458. key = self.get_key(validate, headers)
  459. self.check_response(key, 'key', self.uri)
  460. key.add_user_grant(permission, user_id)
  461. def list_grants(self, headers=None):
  462. self._check_bucket_uri('list_grants ')
  463. bucket = self.get_bucket(headers)
  464. return bucket.list_grants(headers)
  465. def is_file_uri(self):
  466. """Returns True if this URI names a file or directory."""
  467. return False
  468. def is_cloud_uri(self):
  469. """Returns True if this URI names a bucket or object."""
  470. return True
  471. def names_container(self):
  472. """
  473. Returns True if this URI names a directory or bucket. Will return
  474. False for bucket subdirs; providing bucket subdir semantics needs to
  475. be done by the caller (like gsutil does).
  476. """
  477. return bool(not self.object_name)
  478. def names_singleton(self):
  479. """Returns True if this URI names a file or object."""
  480. return bool(self.object_name)
  481. def names_directory(self):
  482. """Returns True if this URI names a directory."""
  483. return False
  484. def names_provider(self):
  485. """Returns True if this URI names a provider."""
  486. return bool(not self.bucket_name)
  487. def names_bucket(self):
  488. """Returns True if this URI names a bucket."""
  489. return bool(self.bucket_name) and bool(not self.object_name)
  490. def names_file(self):
  491. """Returns True if this URI names a file."""
  492. return False
  493. def names_object(self):
  494. """Returns True if this URI names an object."""
  495. return self.names_singleton()
  496. def is_stream(self):
  497. """Returns True if this URI represents input/output stream."""
  498. return False
  499. def create_bucket(self, headers=None, location='', policy=None,
  500. storage_class=None):
  501. self._check_bucket_uri('create_bucket ')
  502. conn = self.connect()
  503. # Pass storage_class param only if this is a GCS bucket. (In S3 the
  504. # storage class is specified on the key object.)
  505. if self.scheme == 'gs':
  506. return conn.create_bucket(self.bucket_name, headers, location, policy,
  507. storage_class)
  508. else:
  509. return conn.create_bucket(self.bucket_name, headers, location, policy)
  510. def delete_bucket(self, headers=None):
  511. self._check_bucket_uri('delete_bucket')
  512. conn = self.connect()
  513. return conn.delete_bucket(self.bucket_name, headers)
  514. def get_all_buckets(self, headers=None):
  515. conn = self.connect()
  516. return conn.get_all_buckets(headers)
  517. def get_provider(self):
  518. conn = self.connect()
  519. provider = conn.provider
  520. self.check_response(provider, 'provider', self.uri)
  521. return provider
  522. def set_acl(self, acl_or_str, key_name='', validate=False, headers=None,
  523. version_id=None, if_generation=None, if_metageneration=None):
  524. """Sets or updates a bucket's ACL."""
  525. self._check_bucket_uri('set_acl')
  526. key_name = key_name or self.object_name or ''
  527. bucket = self.get_bucket(validate, headers)
  528. if self.generation:
  529. bucket.set_acl(
  530. acl_or_str, key_name, headers, generation=self.generation,
  531. if_generation=if_generation, if_metageneration=if_metageneration)
  532. else:
  533. version_id = version_id or self.version_id
  534. bucket.set_acl(acl_or_str, key_name, headers, version_id)
  535. def set_xml_acl(self, xmlstring, key_name='', validate=False, headers=None,
  536. version_id=None, if_generation=None, if_metageneration=None):
  537. """Sets or updates a bucket's ACL with an XML string."""
  538. self._check_bucket_uri('set_xml_acl')
  539. key_name = key_name or self.object_name or ''
  540. bucket = self.get_bucket(validate, headers)
  541. if self.generation:
  542. bucket.set_xml_acl(
  543. xmlstring, key_name, headers, generation=self.generation,
  544. if_generation=if_generation, if_metageneration=if_metageneration)
  545. else:
  546. version_id = version_id or self.version_id
  547. bucket.set_xml_acl(xmlstring, key_name, headers,
  548. version_id=version_id)
  549. def set_def_xml_acl(self, xmlstring, validate=False, headers=None):
  550. """Sets or updates a bucket's default object ACL with an XML string."""
  551. self._check_bucket_uri('set_def_xml_acl')
  552. self.get_bucket(validate, headers).set_def_xml_acl(xmlstring, headers)
  553. def set_def_acl(self, acl_or_str, validate=False, headers=None,
  554. version_id=None):
  555. """Sets or updates a bucket's default object ACL."""
  556. self._check_bucket_uri('set_def_acl')
  557. self.get_bucket(validate, headers).set_def_acl(acl_or_str, headers)
  558. def set_canned_acl(self, acl_str, validate=False, headers=None,
  559. version_id=None):
  560. """Sets or updates a bucket's acl to a predefined (canned) value."""
  561. self._check_object_uri('set_canned_acl')
  562. self._warn_about_args('set_canned_acl', version_id=version_id)
  563. key = self.get_key(validate, headers)
  564. self.check_response(key, 'key', self.uri)
  565. key.set_canned_acl(acl_str, headers)
  566. def set_def_canned_acl(self, acl_str, validate=False, headers=None,
  567. version_id=None):
  568. """Sets or updates a bucket's default object acl to a predefined
  569. (canned) value."""
  570. self._check_bucket_uri('set_def_canned_acl ')
  571. key = self.get_key(validate, headers)
  572. self.check_response(key, 'key', self.uri)
  573. key.set_def_canned_acl(acl_str, headers, version_id)
  574. def set_subresource(self, subresource, value, validate=False, headers=None,
  575. version_id=None):
  576. self._check_bucket_uri('set_subresource')
  577. bucket = self.get_bucket(validate, headers)
  578. bucket.set_subresource(subresource, value, self.object_name, headers,
  579. version_id)
  580. def set_contents_from_string(self, s, headers=None, replace=True,
  581. cb=None, num_cb=10, policy=None, md5=None,
  582. reduced_redundancy=False):
  583. self._check_object_uri('set_contents_from_string')
  584. key = self.new_key(headers=headers)
  585. if self.scheme == 'gs':
  586. if reduced_redundancy:
  587. sys.stderr.write('Warning: GCS does not support '
  588. 'reduced_redundancy; argument ignored by '
  589. 'set_contents_from_string')
  590. result = key.set_contents_from_string(
  591. s, headers, replace, cb, num_cb, policy, md5)
  592. else:
  593. result = key.set_contents_from_string(
  594. s, headers, replace, cb, num_cb, policy, md5,
  595. reduced_redundancy)
  596. self._update_from_key(key)
  597. return result
  598. def set_contents_from_file(self, fp, headers=None, replace=True, cb=None,
  599. num_cb=10, policy=None, md5=None, size=None,
  600. rewind=False, res_upload_handler=None):
  601. self._check_object_uri('set_contents_from_file')
  602. key = self.new_key(headers=headers)
  603. if self.scheme == 'gs':
  604. result = key.set_contents_from_file(
  605. fp, headers, replace, cb, num_cb, policy, md5, size=size,
  606. rewind=rewind, res_upload_handler=res_upload_handler)
  607. if res_upload_handler:
  608. self._update_from_values(None, res_upload_handler.generation,
  609. None, md5)
  610. else:
  611. self._warn_about_args('set_contents_from_file',
  612. res_upload_handler=res_upload_handler)
  613. result = key.set_contents_from_file(
  614. fp, headers, replace, cb, num_cb, policy, md5, size=size,
  615. rewind=rewind)
  616. self._update_from_key(key)
  617. return result
  618. def set_contents_from_stream(self, fp, headers=None, replace=True, cb=None,
  619. policy=None, reduced_redundancy=False):
  620. self._check_object_uri('set_contents_from_stream')
  621. dst_key = self.new_key(False, headers)
  622. result = dst_key.set_contents_from_stream(
  623. fp, headers, replace, cb, policy=policy,
  624. reduced_redundancy=reduced_redundancy)
  625. self._update_from_key(dst_key)
  626. return result
  627. def copy_key(self, src_bucket_name, src_key_name, metadata=None,
  628. src_version_id=None, storage_class='STANDARD',
  629. preserve_acl=False, encrypt_key=False, headers=None,
  630. query_args=None, src_generation=None):
  631. """Returns newly created key."""
  632. self._check_object_uri('copy_key')
  633. dst_bucket = self.get_bucket(validate=False, headers=headers)
  634. if src_generation:
  635. return dst_bucket.copy_key(
  636. new_key_name=self.object_name,
  637. src_bucket_name=src_bucket_name,
  638. src_key_name=src_key_name, metadata=metadata,
  639. storage_class=storage_class, preserve_acl=preserve_acl,
  640. encrypt_key=encrypt_key, headers=headers, query_args=query_args,
  641. src_generation=src_generation)
  642. else:
  643. return dst_bucket.copy_key(
  644. new_key_name=self.object_name,
  645. src_bucket_name=src_bucket_name, src_key_name=src_key_name,
  646. metadata=metadata, src_version_id=src_version_id,
  647. storage_class=storage_class, preserve_acl=preserve_acl,
  648. encrypt_key=encrypt_key, headers=headers, query_args=query_args)
  649. def enable_logging(self, target_bucket, target_prefix=None, validate=False,
  650. headers=None, version_id=None):
  651. self._check_bucket_uri('enable_logging')
  652. bucket = self.get_bucket(validate, headers)
  653. bucket.enable_logging(target_bucket, target_prefix, headers=headers)
  654. def disable_logging(self, validate=False, headers=None, version_id=None):
  655. self._check_bucket_uri('disable_logging')
  656. bucket = self.get_bucket(validate, headers)
  657. bucket.disable_logging(headers=headers)
  658. def get_logging_config(self, validate=False, headers=None, version_id=None):
  659. self._check_bucket_uri('get_logging_config')
  660. bucket = self.get_bucket(validate, headers)
  661. return bucket.get_logging_config(headers=headers)
  662. def set_website_config(self, main_page_suffix=None, error_key=None,
  663. validate=False, headers=None):
  664. self._check_bucket_uri('set_website_config')
  665. bucket = self.get_bucket(validate, headers)
  666. if not (main_page_suffix or error_key):
  667. bucket.delete_website_configuration(headers)
  668. else:
  669. bucket.configure_website(main_page_suffix, error_key, headers)
  670. def get_website_config(self, validate=False, headers=None):
  671. self._check_bucket_uri('get_website_config')
  672. bucket = self.get_bucket(validate, headers)
  673. return bucket.get_website_configuration(headers)
  674. def get_versioning_config(self, headers=None):
  675. self._check_bucket_uri('get_versioning_config')
  676. bucket = self.get_bucket(False, headers)
  677. return bucket.get_versioning_status(headers)
  678. def configure_versioning(self, enabled, headers=None):
  679. self._check_bucket_uri('configure_versioning')
  680. bucket = self.get_bucket(False, headers)
  681. return bucket.configure_versioning(enabled, headers)
  682. def set_metadata(self, metadata_plus, metadata_minus, preserve_acl,
  683. headers=None):
  684. return self.get_key(False).set_remote_metadata(metadata_plus,
  685. metadata_minus,
  686. preserve_acl,
  687. headers=headers)
  688. def compose(self, components, content_type=None, headers=None):
  689. self._check_object_uri('compose')
  690. component_keys = []
  691. for suri in components:
  692. component_keys.append(suri.new_key())
  693. component_keys[-1].generation = suri.generation
  694. self.generation = self.new_key().compose(
  695. component_keys, content_type=content_type, headers=headers)
  696. self._build_uri_strings()
  697. return self
  698. def get_lifecycle_config(self, validate=False, headers=None):
  699. """Returns a bucket's lifecycle configuration."""
  700. self._check_bucket_uri('get_lifecycle_config')
  701. bucket = self.get_bucket(validate, headers)
  702. lifecycle_config = bucket.get_lifecycle_config(headers)
  703. self.check_response(lifecycle_config, 'lifecycle', self.uri)
  704. return lifecycle_config
  705. def configure_lifecycle(self, lifecycle_config, validate=False,
  706. headers=None):
  707. """Sets or updates a bucket's lifecycle configuration."""
  708. self._check_bucket_uri('configure_lifecycle')
  709. bucket = self.get_bucket(validate, headers)
  710. bucket.configure_lifecycle(lifecycle_config, headers)
  711. def get_billing_config(self, headers=None):
  712. self._check_bucket_uri('get_billing_config')
  713. # billing is defined as a bucket param for GCS, but not for S3.
  714. if self.scheme != 'gs':
  715. raise ValueError('get_billing_config() not supported for %s '
  716. 'URIs.' % self.scheme)
  717. bucket = self.get_bucket(False, headers)
  718. return bucket.get_billing_config(headers)
  719. def configure_billing(self, requester_pays=False, validate=False,
  720. headers=None):
  721. """Sets or updates a bucket's billing configuration."""
  722. self._check_bucket_uri('configure_billing')
  723. # billing is defined as a bucket param for GCS, but not for S3.
  724. if self.scheme != 'gs':
  725. raise ValueError('configure_billing() not supported for %s '
  726. 'URIs.' % self.scheme)
  727. bucket = self.get_bucket(validate, headers)
  728. bucket.configure_billing(requester_pays=requester_pays, headers=headers)
  729. def get_encryption_config(self, validate=False, headers=None):
  730. """Returns a GCS bucket's encryption configuration."""
  731. self._check_bucket_uri('get_encryption_config')
  732. # EncryptionConfiguration is defined as a bucket param for GCS, but not
  733. # for S3.
  734. if self.scheme != 'gs':
  735. raise ValueError('get_encryption_config() not supported for %s '
  736. 'URIs.' % self.scheme)
  737. bucket = self.get_bucket(validate, headers)
  738. return bucket.get_encryption_config(headers=headers)
  739. def set_encryption_config(self, default_kms_key_name=None, validate=False,
  740. headers=None):
  741. """Sets a GCS bucket's encryption configuration."""
  742. self._check_bucket_uri('set_encryption_config')
  743. bucket = self.get_bucket(validate, headers)
  744. bucket.set_encryption_config(default_kms_key_name=default_kms_key_name,
  745. headers=headers)
  746. def exists(self, headers=None):
  747. """Returns True if the object exists or False if it doesn't"""
  748. if not self.object_name:
  749. raise InvalidUriError('exists on object-less URI (%s)' % self.uri)
  750. bucket = self.get_bucket(headers)
  751. key = bucket.get_key(self.object_name, headers=headers)
  752. return bool(key)
  753. class FileStorageUri(StorageUri):
  754. """
  755. StorageUri subclass that handles files in the local file system.
  756. Callers should instantiate this class by calling boto.storage_uri().
  757. See file/README about how we map StorageUri operations onto a file system.
  758. """
  759. delim = os.sep
  760. def __init__(self, object_name, debug, is_stream=False):
  761. """Instantiate a FileStorageUri from a path name.
  762. @type object_name: string
  763. @param object_name: object name
  764. @type debug: boolean
  765. @param debug: whether to enable debugging on this StorageUri
  766. After instantiation the components are available in the following
  767. fields: uri, scheme, bucket_name (always blank for this "anonymous"
  768. bucket), object_name.
  769. """
  770. self.scheme = 'file'
  771. self.bucket_name = ''
  772. self.object_name = object_name
  773. self.uri = 'file://' + object_name
  774. self.debug = debug
  775. self.stream = is_stream
  776. def clone_replace_name(self, new_name):
  777. """Instantiate a FileStorageUri from the current FileStorageUri,
  778. but replacing the object_name.
  779. @type new_name: string
  780. @param new_name: new object name
  781. """
  782. return FileStorageUri(new_name, self.debug, self.stream)
  783. def is_file_uri(self):
  784. """Returns True if this URI names a file or directory."""
  785. return True
  786. def is_cloud_uri(self):
  787. """Returns True if this URI names a bucket or object."""
  788. return False
  789. def names_container(self):
  790. """Returns True if this URI names a directory or bucket."""
  791. return self.names_directory()
  792. def names_singleton(self):
  793. """Returns True if this URI names a file (or stream) or object."""
  794. return not self.names_container()
  795. def names_directory(self):
  796. """Returns True if this URI names a directory."""
  797. if self.stream:
  798. return False
  799. return os.path.isdir(self.object_name)
  800. def names_provider(self):
  801. """Returns True if this URI names a provider."""
  802. return False
  803. def names_bucket(self):
  804. """Returns True if this URI names a bucket."""
  805. return False
  806. def names_file(self):
  807. """Returns True if this URI names a file."""
  808. return self.names_singleton()
  809. def names_object(self):
  810. """Returns True if this URI names an object."""
  811. return False
  812. def is_stream(self):
  813. """Returns True if this URI represents input/output stream.
  814. """
  815. return bool(self.stream)
  816. def close(self):
  817. """Closes the underlying file.
  818. """
  819. self.get_key().close()
  820. def exists(self, _headers_not_used=None):
  821. """Returns True if the file exists or False if it doesn't"""
  822. # The _headers_not_used parameter is ignored. It is only there to ensure
  823. # that this method's signature is identical to the exists method on the
  824. # BucketStorageUri class.
  825. return os.path.exists(self.object_name)