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