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 25 26 27class CloudApiDelegator(CloudApi): 28 """Class that handles delegating requests to gsutil Cloud API implementations. 29 30 This class is responsible for determining at runtime which gsutil Cloud API 31 implementation should service the request based on the Cloud storage provider, 32 command-level API support, and configuration file override. 33 34 During initialization it takes as an argument a gsutil_api_map which maps 35 providers to their default and supported gsutil Cloud API implementations 36 (see comments in cs_api_map for details). 37 38 Instantiation of multiple delegators per-thread is required for multiprocess 39 and/or multithreaded operations. Calling methods on the same delegator in 40 multiple threads is unsafe. 41 """ 42 43 def __init__(self, bucket_storage_uri_class, gsutil_api_map, logger, 44 provider=None, debug=0, trace_token=None): 45 """Performs necessary setup for delegating cloud storage requests. 46 47 This function has different arguments than the gsutil Cloud API __init__ 48 function because of the delegation responsibilties of this class. 49 50 Args: 51 bucket_storage_uri_class: boto storage_uri class, used by APIs that 52 provide boto translation or mocking. 53 gsutil_api_map: Map of providers and API selector tuples to api classes 54 which can be used to communicate with those providers. 55 logger: logging.logger for outputting log messages. 56 provider: Default provider prefix describing cloud storage provider to 57 connect to. 58 debug: Debug level for the API implementation (0..3). 59 trace_token: Apiary trace token to pass to API. 60 """ 61 super(CloudApiDelegator, self).__init__(bucket_storage_uri_class, logger, 62 provider=provider, debug=debug, 63 trace_token=trace_token) 64 self.api_map = gsutil_api_map 65 self.prefer_api = boto.config.get('GSUtil', 'prefer_api', '').upper() 66 self.loaded_apis = {} 67 68 if not self.api_map[ApiMapConstants.API_MAP]: 69 raise ArgumentException('No apiclass supplied for gsutil Cloud API map.') 70 71 def _GetApi(self, provider): 72 """Returns a valid CloudApi for use by the caller. 73 74 This function lazy-loads connection and credentials using the API map 75 and credential store provided during class initialization. 76 77 Args: 78 provider: Provider to load API for. If None, class-wide default is used. 79 80 Raises: 81 ArgumentException if there is no matching API available in the API map. 82 83 Returns: 84 Valid API instance that can be used to communicate with the Cloud 85 Storage provider. 86 """ 87 provider = provider or self.provider 88 if not provider: 89 raise ArgumentException('No provider selected for _GetApi') 90 91 provider = str(provider) 92 if provider not in self.loaded_apis: 93 self.loaded_apis[provider] = {} 94 95 api_selector = self.GetApiSelector(provider) 96 if api_selector not in self.loaded_apis[provider]: 97 # Need to load the API. 98 self._LoadApi(provider, api_selector) 99 100 return self.loaded_apis[provider][api_selector] 101 102 def _LoadApi(self, provider, api_selector): 103 """Loads a CloudApi into the loaded_apis map for this class. 104 105 Args: 106 provider: Provider to load the API for. 107 api_selector: cs_api_map.ApiSelector defining the API type. 108 """ 109 if provider not in self.api_map[ApiMapConstants.API_MAP]: 110 raise ArgumentException( 111 'gsutil Cloud API map contains no entry for provider %s.' % provider) 112 if api_selector not in self.api_map[ApiMapConstants.API_MAP][provider]: 113 raise ArgumentException( 114 'gsutil Cloud API map does not support API %s for provider %s.' % 115 (api_selector, provider)) 116 self.loaded_apis[provider][api_selector] = ( 117 self.api_map[ApiMapConstants.API_MAP][provider][api_selector]( 118 self.bucket_storage_uri_class, 119 self.logger, 120 provider=provider, 121 debug=self.debug, 122 trace_token=self.trace_token)) 123 124 def GetApiSelector(self, provider=None): 125 """Returns a cs_api_map.ApiSelector based on input and configuration. 126 127 Args: 128 provider: Provider to return the ApiSelector for. If None, class-wide 129 default is used. 130 131 Returns: 132 cs_api_map.ApiSelector that will be used for calls to the delegator 133 for this provider. 134 """ 135 selected_provider = provider or self.provider 136 if not selected_provider: 137 raise ArgumentException('No provider selected for CloudApi') 138 139 if (selected_provider not in self.api_map[ApiMapConstants.DEFAULT_MAP] or 140 self.api_map[ApiMapConstants.DEFAULT_MAP][selected_provider] not in 141 self.api_map[ApiMapConstants.API_MAP][selected_provider]): 142 raise ArgumentException('No default api available for provider %s' % 143 selected_provider) 144 145 if selected_provider not in self.api_map[ApiMapConstants.SUPPORT_MAP]: 146 raise ArgumentException('No supported apis available for provider %s' % 147 selected_provider) 148 149 api = self.api_map[ApiMapConstants.DEFAULT_MAP][selected_provider] 150 151 # If we have only HMAC credentials for Google Cloud Storage, we must use 152 # the XML API as the JSON API does not support HMAC. 153 # 154 # Technically if we have only HMAC credentials, we should still be able to 155 # access public read resources via the JSON API, but the XML API can do 156 # that just as well. It is better to use it than inspect the credentials on 157 # every HTTP call. 158 if (provider == 'gs' and 159 not config.has_option('Credentials', 'gs_oauth2_refresh_token') and 160 not (config.has_option('Credentials', 'gs_service_client_id') 161 and config.has_option('Credentials', 'gs_service_key_file')) and 162 (config.has_option('Credentials', 'gs_access_key_id') 163 and config.has_option('Credentials', 'gs_secret_access_key'))): 164 api = ApiSelector.XML 165 # Try to force the user's preference to a supported API. 166 elif self.prefer_api in (self.api_map[ApiMapConstants.SUPPORT_MAP] 167 [selected_provider]): 168 api = self.prefer_api 169 return api 170 171 # For function docstrings, see CloudApi class. 172 def GetBucket(self, bucket_name, provider=None, fields=None): 173 return self._GetApi(provider).GetBucket(bucket_name, fields=fields) 174 175 def ListBuckets(self, project_id=None, provider=None, fields=None): 176 return self._GetApi(provider).ListBuckets(project_id=project_id, 177 fields=fields) 178 179 def PatchBucket(self, bucket_name, metadata, canned_acl=None, 180 canned_def_acl=None, preconditions=None, provider=None, 181 fields=None): 182 return self._GetApi(provider).PatchBucket( 183 bucket_name, metadata, canned_acl=canned_acl, 184 canned_def_acl=canned_def_acl, preconditions=preconditions, 185 fields=fields) 186 187 def CreateBucket(self, bucket_name, project_id=None, metadata=None, 188 provider=None, fields=None): 189 return self._GetApi(provider).CreateBucket( 190 bucket_name, project_id=project_id, metadata=metadata, fields=fields) 191 192 def DeleteBucket(self, bucket_name, preconditions=None, provider=None): 193 return self._GetApi(provider).DeleteBucket(bucket_name, 194 preconditions=preconditions) 195 196 def ListObjects(self, bucket_name, prefix=None, delimiter=None, 197 all_versions=None, provider=None, fields=None): 198 return self._GetApi(provider).ListObjects( 199 bucket_name, prefix=prefix, delimiter=delimiter, 200 all_versions=all_versions, fields=fields) 201 202 def GetObjectMetadata(self, bucket_name, object_name, generation=None, 203 provider=None, fields=None): 204 return self._GetApi(provider).GetObjectMetadata( 205 bucket_name, object_name, generation=generation, fields=fields) 206 207 def PatchObjectMetadata(self, bucket_name, object_name, metadata, 208 canned_acl=None, generation=None, preconditions=None, 209 provider=None, fields=None): 210 return self._GetApi(provider).PatchObjectMetadata( 211 bucket_name, object_name, metadata, canned_acl=canned_acl, 212 generation=generation, preconditions=preconditions, fields=fields) 213 214 def GetObjectMedia( 215 self, bucket_name, object_name, download_stream, provider=None, 216 generation=None, object_size=None, 217 download_strategy=CloudApi.DownloadStrategy.ONE_SHOT, 218 start_byte=0, end_byte=None, progress_callback=None, 219 serialization_data=None, digesters=None): 220 return self._GetApi(provider).GetObjectMedia( 221 bucket_name, object_name, download_stream, 222 download_strategy=download_strategy, start_byte=start_byte, 223 end_byte=end_byte, generation=generation, object_size=object_size, 224 progress_callback=progress_callback, 225 serialization_data=serialization_data, digesters=digesters) 226 227 def UploadObject(self, upload_stream, object_metadata, size=None, 228 canned_acl=None, preconditions=None, progress_callback=None, 229 provider=None, fields=None): 230 return self._GetApi(provider).UploadObject( 231 upload_stream, object_metadata, size=size, canned_acl=canned_acl, 232 preconditions=preconditions, progress_callback=progress_callback, 233 fields=fields) 234 235 def UploadObjectStreaming(self, upload_stream, object_metadata, 236 canned_acl=None, preconditions=None, 237 progress_callback=None, provider=None, fields=None): 238 return self._GetApi(provider).UploadObjectStreaming( 239 upload_stream, object_metadata, canned_acl=canned_acl, 240 preconditions=preconditions, progress_callback=progress_callback, 241 fields=fields) 242 243 def UploadObjectResumable( 244 self, upload_stream, object_metadata, canned_acl=None, preconditions=None, 245 provider=None, fields=None, size=None, serialization_data=None, 246 tracker_callback=None, progress_callback=None): 247 return self._GetApi(provider).UploadObjectResumable( 248 upload_stream, object_metadata, canned_acl=canned_acl, 249 preconditions=preconditions, size=size, fields=fields, 250 serialization_data=serialization_data, 251 tracker_callback=tracker_callback, progress_callback=progress_callback) 252 253 def CopyObject(self, src_obj_metadata, dst_obj_metadata, src_generation=None, 254 canned_acl=None, preconditions=None, progress_callback=None, 255 max_bytes_per_call=None, provider=None, fields=None): 256 return self._GetApi(provider).CopyObject( 257 src_obj_metadata, dst_obj_metadata, src_generation=src_generation, 258 canned_acl=canned_acl, preconditions=preconditions, 259 progress_callback=progress_callback, 260 max_bytes_per_call=max_bytes_per_call, fields=fields) 261 262 def ComposeObject(self, src_objs_metadata, dst_obj_metadata, 263 preconditions=None, provider=None, fields=None): 264 return self._GetApi(provider).ComposeObject( 265 src_objs_metadata, dst_obj_metadata, preconditions=preconditions, 266 fields=fields) 267 268 def DeleteObject(self, bucket_name, object_name, preconditions=None, 269 generation=None, provider=None): 270 return self._GetApi(provider).DeleteObject( 271 bucket_name, object_name, preconditions=preconditions, 272 generation=generation) 273 274 def WatchBucket(self, bucket_name, address, channel_id, token=None, 275 provider=None, fields=None): 276 return self._GetApi(provider).WatchBucket( 277 bucket_name, address, channel_id, token=token, fields=fields) 278 279 def StopChannel(self, channel_id, resource_id, provider=None): 280 return self._GetApi(provider).StopChannel(channel_id, resource_id) 281 282 def XmlPassThroughGetAcl(self, storage_url, def_obj_acl=False, provider=None): 283 """XML compatibility function for getting ACLs. 284 285 Args: 286 storage_url: StorageUrl object. 287 def_obj_acl: If true, get the default object ACL on a bucket. 288 provider: Cloud storage provider to connect to. If not present, 289 class-wide default is used. 290 291 Raises: 292 ArgumentException for errors during input validation. 293 ServiceException for errors interacting with cloud storage providers. 294 295 Returns: 296 ACL XML for the resource specified by storage_url. 297 """ 298 return self._GetApi(provider).XmlPassThroughGetAcl(storage_url, 299 def_obj_acl=def_obj_acl) 300 301 def XmlPassThroughSetAcl(self, acl_text, storage_url, canned=True, 302 def_obj_acl=False, provider=None): 303 """XML compatibility function for setting ACLs. 304 305 Args: 306 acl_text: XML ACL or canned ACL string. 307 storage_url: StorageUrl object. 308 canned: If true, acl_text is treated as a canned ACL string. 309 def_obj_acl: If true, set the default object ACL on a bucket. 310 provider: Cloud storage provider to connect to. If not present, 311 class-wide default is used. 312 313 Raises: 314 ArgumentException for errors during input validation. 315 ServiceException for errors interacting with cloud storage providers. 316 317 Returns: 318 None. 319 """ 320 self._GetApi(provider).XmlPassThroughSetAcl( 321 acl_text, storage_url, canned=canned, def_obj_acl=def_obj_acl) 322 323 def XmlPassThroughGetCors(self, storage_url, provider=None): 324 """XML compatibility function for getting CORS configuration on a bucket. 325 326 Args: 327 storage_url: StorageUrl object. 328 provider: Cloud storage provider to connect to. If not present, 329 class-wide default is used. 330 331 Raises: 332 ArgumentException for errors during input validation. 333 ServiceException for errors interacting with cloud storage providers. 334 335 Returns: 336 CORS configuration XML for the bucket specified by storage_url. 337 """ 338 return self._GetApi(provider).XmlPassThroughGetCors(storage_url) 339 340 def XmlPassThroughSetCors(self, cors_text, storage_url, provider=None): 341 """XML compatibility function for setting CORS configuration on a bucket. 342 343 Args: 344 cors_text: Raw CORS XML string. 345 storage_url: StorageUrl object. 346 provider: Cloud storage provider to connect to. If not present, 347 class-wide default is used. 348 349 Raises: 350 ArgumentException for errors during input validation. 351 ServiceException for errors interacting with cloud storage providers. 352 353 Returns: 354 None. 355 """ 356 self._GetApi(provider).XmlPassThroughSetCors(cors_text, storage_url) 357 358 def XmlPassThroughGetLifecycle(self, storage_url, provider=None): 359 """XML compatibility function for getting lifecycle config on a bucket. 360 361 Args: 362 storage_url: StorageUrl object. 363 provider: Cloud storage provider to connect to. If not present, 364 class-wide default is used. 365 366 Raises: 367 ArgumentException for errors during input validation. 368 ServiceException for errors interacting with cloud storage providers. 369 370 Returns: 371 Lifecycle configuration XML for the bucket specified by storage_url. 372 """ 373 return self._GetApi(provider).XmlPassThroughGetLifecycle(storage_url) 374 375 def XmlPassThroughSetLifecycle(self, lifecycle_text, storage_url, 376 provider=None): 377 """XML compatibility function for setting CORS configuration on a bucket. 378 379 Args: 380 lifecycle_text: Raw lifecycle configuration XML string. 381 storage_url: StorageUrl object. 382 provider: Cloud storage provider to connect to. If not present, 383 class-wide default is used. 384 385 Raises: 386 ArgumentException for errors during input validation. 387 ServiceException for errors interacting with cloud storage providers. 388 389 Returns: 390 None. 391 """ 392 self._GetApi(provider).XmlPassThroughSetLifecycle(lifecycle_text, 393 storage_url) 394 395 def XmlPassThroughGetLogging(self, storage_url, provider=None): 396 """XML compatibility function for getting logging configuration on a bucket. 397 398 Args: 399 storage_url: StorageUrl object. 400 provider: Cloud storage provider to connect to. If not present, 401 class-wide default is used. 402 403 Raises: 404 ArgumentException for errors during input validation. 405 ServiceException for errors interacting with cloud storage providers. 406 407 Returns: 408 Logging configuration XML for the bucket specified by storage_url. 409 """ 410 return self._GetApi(provider).XmlPassThroughGetLogging(storage_url) 411 412 def XmlPassThroughGetWebsite(self, storage_url, provider=None): 413 """XML compatibility function for getting website configuration on a bucket. 414 415 Args: 416 storage_url: StorageUrl object. 417 provider: Cloud storage provider to connect to. If not present, 418 class-wide default is used. 419 420 Raises: 421 ArgumentException for errors during input validation. 422 ServiceException for errors interacting with cloud storage providers. 423 424 Returns: 425 Website configuration XML for the bucket specified by storage_url. 426 """ 427 return self._GetApi(provider).XmlPassThroughGetWebsite(storage_url) 428 429