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