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"""Gsutil API delegator for interacting with cloud storage providers."""
16
17from __future__ import absolute_import
18
19import boto
20from boto import config
21from gslib.cloud_api import ArgumentException
22from gslib.cloud_api import CloudApi
23from gslib.cs_api_map import ApiMapConstants
24from gslib.cs_api_map import ApiSelector
25from gslib.exception import CommandException
26
27
28class CloudApiDelegator(CloudApi):
29  """Class that handles delegating requests to gsutil Cloud API implementations.
30
31  This class is responsible for determining at runtime which gsutil Cloud API
32  implementation should service the request based on the Cloud storage provider,
33  command-level API support, and configuration file override.
34
35  During initialization it takes as an argument a gsutil_api_map which maps
36  providers to their default and supported gsutil Cloud API implementations
37  (see comments in cs_api_map for details).
38
39  Instantiation of multiple delegators per-thread is required for multiprocess
40  and/or multithreaded operations. Calling methods on the same delegator in
41  multiple threads is unsafe.
42  """
43
44  def __init__(self, bucket_storage_uri_class, gsutil_api_map, logger,
45               status_queue, provider=None, debug=0, trace_token=None,
46               perf_trace_token=None, user_project=None):
47    """Performs necessary setup for delegating cloud storage requests.
48
49    This function has different arguments than the gsutil Cloud API __init__
50    function because of the delegation responsibilties of this class.
51
52    Args:
53      bucket_storage_uri_class: boto storage_uri class, used by APIs that
54                                provide boto translation or mocking.
55      gsutil_api_map: Map of providers and API selector tuples to api classes
56                      which can be used to communicate with those providers.
57      logger: logging.logger for outputting log messages.
58      status_queue: Queue for relaying status to UI.
59      provider: Default provider prefix describing cloud storage provider to
60                connect to.
61      debug: Debug level for the API implementation (0..3).
62      trace_token: Apiary trace token to pass to API.
63      perf_trace_token: Performance trace token to use when making API calls.
64      user_project: Project to be billed for this project.
65    """
66    super(CloudApiDelegator, self).__init__(bucket_storage_uri_class, logger,
67                                            status_queue,
68                                            provider=provider, debug=debug,
69                                            trace_token=trace_token,
70                                            perf_trace_token=perf_trace_token,
71                                            user_project=user_project)
72    self.api_map = gsutil_api_map
73    self.prefer_api = boto.config.get('GSUtil', 'prefer_api', '').upper()
74    self.loaded_apis = {}
75
76    if not self.api_map[ApiMapConstants.API_MAP]:
77      raise ArgumentException('No apiclass supplied for gsutil Cloud API map.')
78
79  def _GetApi(self, provider):
80    """Returns a valid CloudApi for use by the caller.
81
82    This function lazy-loads connection and credentials using the API map
83    and credential store provided during class initialization.
84
85    Args:
86      provider: Provider to load API for. If None, class-wide default is used.
87
88    Raises:
89      ArgumentException if there is no matching API available in the API map.
90
91    Returns:
92      Valid API instance that can be used to communicate with the Cloud
93      Storage provider.
94    """
95    provider = provider or self.provider
96    if not provider:
97      raise ArgumentException('No provider selected for _GetApi')
98
99    provider = str(provider)
100    if provider not in self.loaded_apis:
101      self.loaded_apis[provider] = {}
102
103    api_selector = self.GetApiSelector(provider)
104    if api_selector not in self.loaded_apis[provider]:
105      # Need to load the API.
106      self._LoadApi(provider, api_selector)
107
108    return self.loaded_apis[provider][api_selector]
109
110  def _LoadApi(self, provider, api_selector):
111    """Loads a CloudApi into the loaded_apis map for this class.
112
113    Args:
114      provider: Provider to load the API for.
115      api_selector: cs_api_map.ApiSelector defining the API type.
116    """
117    if provider not in self.api_map[ApiMapConstants.API_MAP]:
118      raise ArgumentException(
119          'gsutil Cloud API map contains no entry for provider %s.' % provider)
120    if api_selector not in self.api_map[ApiMapConstants.API_MAP][provider]:
121      raise ArgumentException(
122          'gsutil Cloud API map does not support API %s for provider %s.' %
123          (api_selector, provider))
124    self.loaded_apis[provider][api_selector] = (
125        self.api_map[ApiMapConstants.API_MAP][provider][api_selector](
126            self.bucket_storage_uri_class,
127            self.logger,
128            self.status_queue,
129            provider=provider,
130            debug=self.debug,
131            trace_token=self.trace_token,
132            perf_trace_token=self.perf_trace_token,
133            user_project=self.user_project))
134
135  def GetApiSelector(self, provider=None):
136    """Returns a cs_api_map.ApiSelector based on input and configuration.
137
138    Args:
139      provider: Provider to return the ApiSelector for.  If None, class-wide
140                default is used.
141
142    Returns:
143      cs_api_map.ApiSelector that will be used for calls to the delegator
144      for this provider.
145    """
146    selected_provider = provider or self.provider
147    if not selected_provider:
148      raise ArgumentException('No provider selected for CloudApi')
149
150    if (selected_provider not in self.api_map[ApiMapConstants.DEFAULT_MAP] or
151        self.api_map[ApiMapConstants.DEFAULT_MAP][selected_provider] not in
152        self.api_map[ApiMapConstants.API_MAP][selected_provider]):
153      raise ArgumentException('No default api available for provider %s' %
154                              selected_provider)
155
156    if selected_provider not in self.api_map[ApiMapConstants.SUPPORT_MAP]:
157      raise ArgumentException('No supported apis available for provider %s' %
158                              selected_provider)
159
160    api = self.api_map[ApiMapConstants.DEFAULT_MAP][selected_provider]
161
162    using_gs_hmac = (
163        provider == 'gs' and
164        not config.has_option('Credentials', 'gs_oauth2_refresh_token') and
165        not (config.has_option('Credentials', 'gs_service_client_id')
166             and config.has_option('Credentials', 'gs_service_key_file')) and
167        (config.has_option('Credentials', 'gs_access_key_id')
168         and config.has_option('Credentials', 'gs_secret_access_key')))
169
170    configured_encryption = (
171        provider == 'gs' and
172        (config.has_option('GSUtil', 'encryption_key') or
173         config.has_option('GSUtil', 'decryption_key1')))
174
175    if using_gs_hmac and configured_encryption:
176      raise CommandException(
177          'gsutil does not support HMAC credentials with customer-supplied '
178          'encryption keys. Please generate and include non-HMAC credentials '
179          'in your .boto configuration file, or to access public encrypted '
180          'objects, remove your HMAC credentials.')
181    # If we have only HMAC credentials for Google Cloud Storage, we must use
182    # the XML API as the JSON API does not support HMAC.
183    #
184    # Technically if we have only HMAC credentials, we should still be able to
185    # access public read resources via the JSON API, but the XML API can do
186    # that just as well. It is better to use it than inspect the credentials on
187    # every HTTP call.
188    elif using_gs_hmac:
189      api = ApiSelector.XML
190    # Customer-supplied encryption keys are currently only supported in the
191    # JSON API implementation (GcsJsonApi). We can't stop XML API users from
192    # interacting with encrypted objects, since we don't know the object is
193    # encrypted until after the API call is made, but if they specify
194    # configuration values we will use JSON.
195    elif configured_encryption:
196      api = ApiSelector.JSON
197    # Try to force the user's preference to a supported API.
198    elif self.prefer_api in (self.api_map[ApiMapConstants.SUPPORT_MAP]
199                             [selected_provider]):
200      api = self.prefer_api
201    return api
202
203  # For function docstrings, see CloudApi class.
204  def GetBucket(self, bucket_name, provider=None, fields=None):
205    return self._GetApi(provider).GetBucket(bucket_name, fields=fields)
206
207  def GetBucketIamPolicy(self, bucket_name, provider=None, fields=None):
208    return self._GetApi(provider).GetBucketIamPolicy(bucket_name, fields=fields)
209
210  def SetBucketIamPolicy(self, bucket_name, policy, provider=None):
211    return self._GetApi(provider).SetBucketIamPolicy(bucket_name, policy)
212
213  def ListBuckets(self, project_id=None, provider=None, fields=None):
214    return self._GetApi(provider).ListBuckets(project_id=project_id,
215                                              fields=fields)
216
217  def PatchBucket(self, bucket_name, metadata, canned_acl=None,
218                  canned_def_acl=None, preconditions=None, provider=None,
219                  fields=None):
220    return self._GetApi(provider).PatchBucket(
221        bucket_name, metadata, canned_acl=canned_acl,
222        canned_def_acl=canned_def_acl, preconditions=preconditions,
223        fields=fields)
224
225  def CreateBucket(self, bucket_name, project_id=None, metadata=None,
226                   provider=None, fields=None):
227    return self._GetApi(provider).CreateBucket(
228        bucket_name, project_id=project_id, metadata=metadata, fields=fields)
229
230  def DeleteBucket(self, bucket_name, preconditions=None, provider=None):
231    return self._GetApi(provider).DeleteBucket(bucket_name,
232                                               preconditions=preconditions)
233
234  def GetObjectIamPolicy(self, bucket_name, object_name,
235                         generation=None, provider=None, fields=None):
236    return self._GetApi(provider).GetObjectIamPolicy(
237        bucket_name, object_name, generation, fields=fields)
238
239  def SetObjectIamPolicy(self, bucket_name, object_name, policy,
240                         generation=None, provider=None):
241    return self._GetApi(provider).SetObjectIamPolicy(
242        bucket_name, object_name, policy, generation)
243
244  def ListObjects(self, bucket_name, prefix=None, delimiter=None,
245                  all_versions=None, provider=None, fields=None):
246    return self._GetApi(provider).ListObjects(
247        bucket_name, prefix=prefix, delimiter=delimiter,
248        all_versions=all_versions, fields=fields)
249
250  def GetObjectMetadata(self, bucket_name, object_name, generation=None,
251                        provider=None, fields=None):
252    return self._GetApi(provider).GetObjectMetadata(
253        bucket_name, object_name, generation=generation, fields=fields)
254
255  def PatchObjectMetadata(self, bucket_name, object_name, metadata,
256                          canned_acl=None, generation=None, preconditions=None,
257                          provider=None, fields=None):
258    return self._GetApi(provider).PatchObjectMetadata(
259        bucket_name, object_name, metadata, canned_acl=canned_acl,
260        generation=generation, preconditions=preconditions, fields=fields)
261
262  def GetObjectMedia(
263      self, bucket_name, object_name, download_stream, provider=None,
264      generation=None, object_size=None,
265      compressed_encoding=False,
266      download_strategy=CloudApi.DownloadStrategy.ONE_SHOT,
267      start_byte=0, end_byte=None, progress_callback=None,
268      serialization_data=None, digesters=None, decryption_tuple=None):
269    return self._GetApi(provider).GetObjectMedia(
270        bucket_name, object_name, download_stream,
271        compressed_encoding=compressed_encoding,
272        download_strategy=download_strategy, start_byte=start_byte,
273        end_byte=end_byte, generation=generation, object_size=object_size,
274        progress_callback=progress_callback,
275        serialization_data=serialization_data, digesters=digesters,
276        decryption_tuple=decryption_tuple)
277
278  def UploadObject(self, upload_stream, object_metadata, size=None,
279                   canned_acl=None, preconditions=None, progress_callback=None,
280                   encryption_tuple=None, provider=None, fields=None):
281    return self._GetApi(provider).UploadObject(
282        upload_stream, object_metadata, size=size, canned_acl=canned_acl,
283        preconditions=preconditions, progress_callback=progress_callback,
284        encryption_tuple=encryption_tuple, fields=fields)
285
286  def UploadObjectStreaming(self, upload_stream, object_metadata,
287                            canned_acl=None, preconditions=None,
288                            progress_callback=None, encryption_tuple=None,
289                            provider=None, fields=None):
290    return self._GetApi(provider).UploadObjectStreaming(
291        upload_stream, object_metadata, canned_acl=canned_acl,
292        preconditions=preconditions, progress_callback=progress_callback,
293        encryption_tuple=encryption_tuple, fields=fields)
294
295  def UploadObjectResumable(
296      self, upload_stream, object_metadata, canned_acl=None, preconditions=None,
297      size=None, serialization_data=None, tracker_callback=None,
298      progress_callback=None, encryption_tuple=None, provider=None,
299      fields=None):
300    return self._GetApi(provider).UploadObjectResumable(
301        upload_stream, object_metadata, canned_acl=canned_acl,
302        preconditions=preconditions, size=size,
303        serialization_data=serialization_data,
304        tracker_callback=tracker_callback, progress_callback=progress_callback,
305        encryption_tuple=encryption_tuple, fields=fields)
306
307  def CopyObject(self, src_obj_metadata, dst_obj_metadata, src_generation=None,
308                 canned_acl=None, preconditions=None, progress_callback=None,
309                 max_bytes_per_call=None, encryption_tuple=None,
310                 decryption_tuple=None, provider=None, fields=None):
311    return self._GetApi(provider).CopyObject(
312        src_obj_metadata, dst_obj_metadata, src_generation=src_generation,
313        canned_acl=canned_acl, preconditions=preconditions,
314        progress_callback=progress_callback,
315        max_bytes_per_call=max_bytes_per_call,
316        encryption_tuple=encryption_tuple, decryption_tuple=decryption_tuple,
317        fields=fields)
318
319  def ComposeObject(self, src_objs_metadata, dst_obj_metadata,
320                    preconditions=None, encryption_tuple=None, provider=None,
321                    fields=None):
322    return self._GetApi(provider).ComposeObject(
323        src_objs_metadata, dst_obj_metadata, preconditions=preconditions,
324        encryption_tuple=encryption_tuple, fields=fields)
325
326  def DeleteObject(self, bucket_name, object_name, preconditions=None,
327                   generation=None, provider=None):
328    return self._GetApi(provider).DeleteObject(
329        bucket_name, object_name, preconditions=preconditions,
330        generation=generation)
331
332  def WatchBucket(self, bucket_name, address, channel_id, token=None,
333                  provider=None, fields=None):
334    return self._GetApi(provider).WatchBucket(
335        bucket_name, address, channel_id, token=token, fields=fields)
336
337  def StopChannel(self, channel_id, resource_id, provider=None):
338    return self._GetApi(provider).StopChannel(channel_id, resource_id)
339
340  def GetProjectServiceAccount(self, project_number, provider=None):
341    return self._GetApi(provider).GetProjectServiceAccount(project_number)
342
343  def CreateNotificationConfig(
344      self, bucket_name, pubsub_topic, payload_format, event_types=None,
345      custom_attributes=None, object_name_prefix=None, provider=None):
346    return self._GetApi(provider).CreateNotificationConfig(
347        bucket_name, pubsub_topic, payload_format, event_types,
348        custom_attributes, object_name_prefix)
349
350  def DeleteNotificationConfig(
351      self, bucket_name, notification, provider=None):
352    return self._GetApi(provider).DeleteNotificationConfig(
353        bucket_name, notification)
354
355  def ListNotificationConfigs(self, bucket_name, provider=None):
356    return self._GetApi(provider).ListNotificationConfigs(bucket_name)
357
358  def XmlPassThroughGetAcl(self, storage_url, def_obj_acl=False, provider=None):
359    """XML compatibility function for getting ACLs.
360
361    Args:
362      storage_url: StorageUrl object.
363      def_obj_acl: If true, get the default object ACL on a bucket.
364      provider: Cloud storage provider to connect to.  If not present,
365                class-wide default is used.
366
367    Raises:
368      ArgumentException for errors during input validation.
369      ServiceException for errors interacting with cloud storage providers.
370
371    Returns:
372      ACL XML for the resource specified by storage_url.
373    """
374    return self._GetApi(provider).XmlPassThroughGetAcl(storage_url,
375                                                       def_obj_acl=def_obj_acl)
376
377  def XmlPassThroughSetAcl(self, acl_text, storage_url, canned=True,
378                           def_obj_acl=False, provider=None):
379    """XML compatibility function for setting ACLs.
380
381    Args:
382      acl_text: XML ACL or canned ACL string.
383      storage_url: StorageUrl object.
384      canned: If true, acl_text is treated as a canned ACL string.
385      def_obj_acl: If true, set the default object ACL on a bucket.
386      provider: Cloud storage provider to connect to.  If not present,
387                class-wide default is used.
388
389    Raises:
390      ArgumentException for errors during input validation.
391      ServiceException for errors interacting with cloud storage providers.
392
393    Returns:
394      None.
395    """
396    self._GetApi(provider).XmlPassThroughSetAcl(
397        acl_text, storage_url, canned=canned, def_obj_acl=def_obj_acl)
398
399  def XmlPassThroughGetCors(self, storage_url, provider=None):
400    """XML compatibility function for getting CORS configuration on a bucket.
401
402    Args:
403      storage_url: StorageUrl object.
404      provider: Cloud storage provider to connect to.  If not present,
405                class-wide default is used.
406
407    Raises:
408      ArgumentException for errors during input validation.
409      ServiceException for errors interacting with cloud storage providers.
410
411    Returns:
412      CORS configuration XML for the bucket specified by storage_url.
413    """
414    return self._GetApi(provider).XmlPassThroughGetCors(storage_url)
415
416  def XmlPassThroughSetCors(self, cors_text, storage_url, provider=None):
417    """XML compatibility function for setting CORS configuration on a bucket.
418
419    Args:
420      cors_text: Raw CORS XML string.
421      storage_url: StorageUrl object.
422      provider: Cloud storage provider to connect to.  If not present,
423                class-wide default is used.
424
425    Raises:
426      ArgumentException for errors during input validation.
427      ServiceException for errors interacting with cloud storage providers.
428
429    Returns:
430      None.
431    """
432    self._GetApi(provider).XmlPassThroughSetCors(cors_text, storage_url)
433
434  def XmlPassThroughGetLifecycle(self, storage_url, provider=None):
435    """XML compatibility function for getting lifecycle config on a bucket.
436
437    Args:
438      storage_url: StorageUrl object.
439      provider: Cloud storage provider to connect to.  If not present,
440                class-wide default is used.
441
442    Raises:
443      ArgumentException for errors during input validation.
444      ServiceException for errors interacting with cloud storage providers.
445
446    Returns:
447      Lifecycle configuration XML for the bucket specified by storage_url.
448    """
449    return self._GetApi(provider).XmlPassThroughGetLifecycle(storage_url)
450
451  def XmlPassThroughSetLifecycle(self, lifecycle_text, storage_url,
452                                 provider=None):
453    """XML compatibility function for setting lifecycle config on a bucket.
454
455    Args:
456      lifecycle_text: Raw lifecycle configuration XML string.
457      storage_url: StorageUrl object.
458      provider: Cloud storage provider to connect to.  If not present,
459                class-wide default is used.
460
461    Raises:
462      ArgumentException for errors during input validation.
463      ServiceException for errors interacting with cloud storage providers.
464
465    Returns:
466      None.
467    """
468    self._GetApi(provider).XmlPassThroughSetLifecycle(lifecycle_text,
469                                                      storage_url)
470
471  def XmlPassThroughGetLogging(self, storage_url, provider=None):
472    """XML compatibility function for getting logging configuration on a bucket.
473
474    Args:
475      storage_url: StorageUrl object.
476      provider: Cloud storage provider to connect to.  If not present,
477                class-wide default is used.
478
479    Raises:
480      ArgumentException for errors during input validation.
481      ServiceException for errors interacting with cloud storage providers.
482
483    Returns:
484      Logging configuration XML for the bucket specified by storage_url.
485    """
486    return self._GetApi(provider).XmlPassThroughGetLogging(storage_url)
487
488  def XmlPassThroughSetTagging(self, tags_text, storage_url, provider=None):
489    """XML compatibility function for setting tagging configuration on a bucket.
490
491    This passthrough provides support for setting a tagging configuration
492    (equivalent to a label configuration) on a cloud bucket.
493
494    Args:
495      tags_text: Raw tagging configuration XML string.
496      storage_url: StorageUrl object.
497      provider: Cloud storage provider to connect to.  If not present,
498                class-wide default is used.
499
500    Raises:
501      ArgumentException for errors during input validation.
502      ServiceException for errors interacting with cloud storage providers.
503
504    Returns:
505      None.
506    """
507    return self._GetApi(provider).XmlPassThroughSetTagging(
508        tags_text, storage_url)
509
510  def XmlPassThroughGetTagging(self, storage_url, provider=None):
511    """XML compatibility function for getting tagging configuration on a bucket.
512
513    Args:
514      storage_url: StorageUrl object.
515      provider: Cloud storage provider to connect to.  If not present,
516                class-wide default is used.
517
518    Raises:
519      ArgumentException for errors during input validation.
520      ServiceException for errors interacting with cloud storage providers.
521
522    Returns:
523      Tagging configuration XML for the bucket specified by storage_url.
524    """
525    return self._GetApi(provider).XmlPassThroughGetTagging(storage_url)
526
527  def XmlPassThroughGetWebsite(self, storage_url, provider=None):
528    """XML compatibility function for getting website configuration on a bucket.
529
530    Args:
531      storage_url: StorageUrl object.
532      provider: Cloud storage provider to connect to.  If not present,
533                class-wide default is used.
534
535    Raises:
536      ArgumentException for errors during input validation.
537      ServiceException for errors interacting with cloud storage providers.
538
539    Returns:
540      Website configuration XML for the bucket specified by storage_url.
541    """
542    return self._GetApi(provider).XmlPassThroughGetWebsite(storage_url)
543
544