1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"). You 4# may not use this file except in compliance with the License. A copy of 5# the License is located at 6# 7# https://aws.amazon.com/apache2.0/ 8# 9# or in the "license" file accompanying this file. This file is 10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11# ANY KIND, either express or implied. See the License for the specific 12# language governing permissions and limitations under the License. 13 14import copy 15import os 16 17import botocore.session 18from botocore.client import Config 19from botocore.exceptions import DataNotFoundError, UnknownServiceError 20 21import boto3 22import boto3.utils 23from boto3.exceptions import ResourceNotExistsError, UnknownAPIVersionError 24 25from .resources.factory import ResourceFactory 26 27 28class Session(object): 29 """ 30 A session stores configuration state and allows you to create service 31 clients and resources. 32 33 :type aws_access_key_id: string 34 :param aws_access_key_id: AWS access key ID 35 :type aws_secret_access_key: string 36 :param aws_secret_access_key: AWS secret access key 37 :type aws_session_token: string 38 :param aws_session_token: AWS temporary session token 39 :type region_name: string 40 :param region_name: Default region when creating new connections 41 :type botocore_session: botocore.session.Session 42 :param botocore_session: Use this Botocore session instead of creating 43 a new default one. 44 :type profile_name: string 45 :param profile_name: The name of a profile to use. If not given, then 46 the default profile is used. 47 """ 48 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, 49 aws_session_token=None, region_name=None, 50 botocore_session=None, profile_name=None): 51 if botocore_session is not None: 52 self._session = botocore_session 53 else: 54 # Create a new default session 55 self._session = botocore.session.get_session() 56 57 # Setup custom user-agent string if it isn't already customized 58 if self._session.user_agent_name == 'Botocore': 59 botocore_info = 'Botocore/{0}'.format( 60 self._session.user_agent_version) 61 if self._session.user_agent_extra: 62 self._session.user_agent_extra += ' ' + botocore_info 63 else: 64 self._session.user_agent_extra = botocore_info 65 self._session.user_agent_name = 'Boto3' 66 self._session.user_agent_version = boto3.__version__ 67 68 if profile_name is not None: 69 self._session.set_config_variable('profile', profile_name) 70 71 if aws_access_key_id or aws_secret_access_key or aws_session_token: 72 self._session.set_credentials( 73 aws_access_key_id, aws_secret_access_key, aws_session_token) 74 75 if region_name is not None: 76 self._session.set_config_variable('region', region_name) 77 78 self.resource_factory = ResourceFactory( 79 self._session.get_component('event_emitter')) 80 self._setup_loader() 81 self._register_default_handlers() 82 83 def __repr__(self): 84 return '{0}(region_name={1})'.format( 85 self.__class__.__name__, 86 repr(self._session.get_config_variable('region'))) 87 88 @property 89 def profile_name(self): 90 """ 91 The **read-only** profile name. 92 """ 93 return self._session.profile or 'default' 94 95 @property 96 def region_name(self): 97 """ 98 The **read-only** region name. 99 """ 100 return self._session.get_config_variable('region') 101 102 @property 103 def events(self): 104 """ 105 The event emitter for a session 106 """ 107 return self._session.get_component('event_emitter') 108 109 @property 110 def available_profiles(self): 111 """ 112 The profiles available to the session credentials 113 """ 114 return self._session.available_profiles 115 116 def _setup_loader(self): 117 """ 118 Setup loader paths so that we can load resources. 119 """ 120 self._loader = self._session.get_component('data_loader') 121 self._loader.search_paths.append( 122 os.path.join(os.path.dirname(__file__), 'data')) 123 124 def get_available_services(self): 125 """ 126 Get a list of available services that can be loaded as low-level 127 clients via :py:meth:`Session.client`. 128 129 :rtype: list 130 :return: List of service names 131 """ 132 return self._session.get_available_services() 133 134 def get_available_resources(self): 135 """ 136 Get a list of available services that can be loaded as resource 137 clients via :py:meth:`Session.resource`. 138 139 :rtype: list 140 :return: List of service names 141 """ 142 return self._loader.list_available_services(type_name='resources-1') 143 144 def get_available_partitions(self): 145 """Lists the available partitions 146 147 :rtype: list 148 :return: Returns a list of partition names (e.g., ["aws", "aws-cn"]) 149 """ 150 return self._session.get_available_partitions() 151 152 def get_available_regions(self, service_name, partition_name='aws', 153 allow_non_regional=False): 154 """Lists the region and endpoint names of a particular partition. 155 156 :type service_name: string 157 :param service_name: Name of a service to list endpoint for (e.g., s3). 158 159 :type partition_name: string 160 :param partition_name: Name of the partition to limit endpoints to. 161 (e.g., aws for the public AWS endpoints, aws-cn for AWS China 162 endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.) 163 164 :type allow_non_regional: bool 165 :param allow_non_regional: Set to True to include endpoints that are 166 not regional endpoints (e.g., s3-external-1, 167 fips-us-gov-west-1, etc). 168 169 :return: Returns a list of endpoint names (e.g., ["us-east-1"]). 170 """ 171 return self._session.get_available_regions( 172 service_name=service_name, partition_name=partition_name, 173 allow_non_regional=allow_non_regional) 174 175 def get_credentials(self): 176 """ 177 Return the :class:`botocore.credential.Credential` object 178 associated with this session. If the credentials have not 179 yet been loaded, this will attempt to load them. If they 180 have already been loaded, this will return the cached 181 credentials. 182 """ 183 return self._session.get_credentials() 184 185 def client(self, service_name, region_name=None, api_version=None, 186 use_ssl=True, verify=None, endpoint_url=None, 187 aws_access_key_id=None, aws_secret_access_key=None, 188 aws_session_token=None, config=None): 189 """ 190 Create a low-level service client by name. 191 192 :type service_name: string 193 :param service_name: The name of a service, e.g. 's3' or 'ec2'. You 194 can get a list of available services via 195 :py:meth:`get_available_services`. 196 197 :type region_name: string 198 :param region_name: The name of the region associated with the client. 199 A client is associated with a single region. 200 201 :type api_version: string 202 :param api_version: The API version to use. By default, botocore will 203 use the latest API version when creating a client. You only need 204 to specify this parameter if you want to use a previous API version 205 of the client. 206 207 :type use_ssl: boolean 208 :param use_ssl: Whether or not to use SSL. By default, SSL is used. 209 Note that not all services support non-ssl connections. 210 211 :type verify: boolean/string 212 :param verify: Whether or not to verify SSL certificates. By default 213 SSL certificates are verified. You can provide the following 214 values: 215 216 * False - do not validate SSL certificates. SSL will still be 217 used (unless use_ssl is False), but SSL certificates 218 will not be verified. 219 * path/to/cert/bundle.pem - A filename of the CA cert bundle to 220 uses. You can specify this argument if you want to use a 221 different CA cert bundle than the one used by botocore. 222 223 :type endpoint_url: string 224 :param endpoint_url: The complete URL to use for the constructed 225 client. Normally, botocore will automatically construct the 226 appropriate URL to use when communicating with a service. You 227 can specify a complete URL (including the "http/https" scheme) 228 to override this behavior. If this value is provided, 229 then ``use_ssl`` is ignored. 230 231 :type aws_access_key_id: string 232 :param aws_access_key_id: The access key to use when creating 233 the client. This is entirely optional, and if not provided, 234 the credentials configured for the session will automatically 235 be used. You only need to provide this argument if you want 236 to override the credentials used for this specific client. 237 238 :type aws_secret_access_key: string 239 :param aws_secret_access_key: The secret key to use when creating 240 the client. Same semantics as aws_access_key_id above. 241 242 :type aws_session_token: string 243 :param aws_session_token: The session token to use when creating 244 the client. Same semantics as aws_access_key_id above. 245 246 :type config: botocore.client.Config 247 :param config: Advanced client configuration options. If region_name 248 is specified in the client config, its value will take precedence 249 over environment variables and configuration values, but not over 250 a region_name value passed explicitly to the method. See 251 `botocore config documentation 252 <https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html>`_ 253 for more details. 254 255 :return: Service client instance 256 257 """ 258 return self._session.create_client( 259 service_name, region_name=region_name, api_version=api_version, 260 use_ssl=use_ssl, verify=verify, endpoint_url=endpoint_url, 261 aws_access_key_id=aws_access_key_id, 262 aws_secret_access_key=aws_secret_access_key, 263 aws_session_token=aws_session_token, config=config) 264 265 def resource(self, service_name, region_name=None, api_version=None, 266 use_ssl=True, verify=None, endpoint_url=None, 267 aws_access_key_id=None, aws_secret_access_key=None, 268 aws_session_token=None, config=None): 269 """ 270 Create a resource service client by name. 271 272 :type service_name: string 273 :param service_name: The name of a service, e.g. 's3' or 'ec2'. You 274 can get a list of available services via 275 :py:meth:`get_available_resources`. 276 277 :type region_name: string 278 :param region_name: The name of the region associated with the client. 279 A client is associated with a single region. 280 281 :type api_version: string 282 :param api_version: The API version to use. By default, botocore will 283 use the latest API version when creating a client. You only need 284 to specify this parameter if you want to use a previous API version 285 of the client. 286 287 :type use_ssl: boolean 288 :param use_ssl: Whether or not to use SSL. By default, SSL is used. 289 Note that not all services support non-ssl connections. 290 291 :type verify: boolean/string 292 :param verify: Whether or not to verify SSL certificates. By default 293 SSL certificates are verified. You can provide the following 294 values: 295 296 * False - do not validate SSL certificates. SSL will still be 297 used (unless use_ssl is False), but SSL certificates 298 will not be verified. 299 * path/to/cert/bundle.pem - A filename of the CA cert bundle to 300 uses. You can specify this argument if you want to use a 301 different CA cert bundle than the one used by botocore. 302 303 :type endpoint_url: string 304 :param endpoint_url: The complete URL to use for the constructed 305 client. Normally, botocore will automatically construct the 306 appropriate URL to use when communicating with a service. You 307 can specify a complete URL (including the "http/https" scheme) 308 to override this behavior. If this value is provided, 309 then ``use_ssl`` is ignored. 310 311 :type aws_access_key_id: string 312 :param aws_access_key_id: The access key to use when creating 313 the client. This is entirely optional, and if not provided, 314 the credentials configured for the session will automatically 315 be used. You only need to provide this argument if you want 316 to override the credentials used for this specific client. 317 318 :type aws_secret_access_key: string 319 :param aws_secret_access_key: The secret key to use when creating 320 the client. Same semantics as aws_access_key_id above. 321 322 :type aws_session_token: string 323 :param aws_session_token: The session token to use when creating 324 the client. Same semantics as aws_access_key_id above. 325 326 :type config: botocore.client.Config 327 :param config: Advanced client configuration options. If region_name 328 is specified in the client config, its value will take precedence 329 over environment variables and configuration values, but not over 330 a region_name value passed explicitly to the method. If 331 user_agent_extra is specified in the client config, it overrides 332 the default user_agent_extra provided by the resource API. See 333 `botocore config documentation 334 <https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html>`_ 335 for more details. 336 337 :return: Subclass of :py:class:`~boto3.resources.base.ServiceResource` 338 """ 339 try: 340 resource_model = self._loader.load_service_model( 341 service_name, 'resources-1', api_version) 342 except UnknownServiceError: 343 available = self.get_available_resources() 344 has_low_level_client = ( 345 service_name in self.get_available_services()) 346 raise ResourceNotExistsError(service_name, available, 347 has_low_level_client) 348 except DataNotFoundError: 349 # This is because we've provided an invalid API version. 350 available_api_versions = self._loader.list_api_versions( 351 service_name, 'resources-1') 352 raise UnknownAPIVersionError( 353 service_name, api_version, ', '.join(available_api_versions)) 354 355 if api_version is None: 356 # Even though botocore's load_service_model() can handle 357 # using the latest api_version if not provided, we need 358 # to track this api_version in boto3 in order to ensure 359 # we're pairing a resource model with a client model 360 # of the same API version. It's possible for the latest 361 # API version of a resource model in boto3 to not be 362 # the same API version as a service model in botocore. 363 # So we need to look up the api_version if one is not 364 # provided to ensure we load the same API version of the 365 # client. 366 # 367 # Note: This is relying on the fact that 368 # loader.load_service_model(..., api_version=None) 369 # and loader.determine_latest_version(..., 'resources-1') 370 # both load the same api version of the file. 371 api_version = self._loader.determine_latest_version( 372 service_name, 'resources-1') 373 374 # Creating a new resource instance requires the low-level client 375 # and service model, the resource version and resource JSON data. 376 # We pass these to the factory and get back a class, which is 377 # instantiated on top of the low-level client. 378 if config is not None: 379 if config.user_agent_extra is None: 380 config = copy.deepcopy(config) 381 config.user_agent_extra = 'Resource' 382 else: 383 config = Config(user_agent_extra='Resource') 384 client = self.client( 385 service_name, region_name=region_name, api_version=api_version, 386 use_ssl=use_ssl, verify=verify, endpoint_url=endpoint_url, 387 aws_access_key_id=aws_access_key_id, 388 aws_secret_access_key=aws_secret_access_key, 389 aws_session_token=aws_session_token, config=config) 390 service_model = client.meta.service_model 391 392 # Create a ServiceContext object to serve as a reference to 393 # important read-only information about the general service. 394 service_context = boto3.utils.ServiceContext( 395 service_name=service_name, service_model=service_model, 396 resource_json_definitions=resource_model['resources'], 397 service_waiter_model=boto3.utils.LazyLoadedWaiterModel( 398 self._session, service_name, api_version 399 ) 400 ) 401 402 # Create the service resource class. 403 cls = self.resource_factory.load_from_definition( 404 resource_name=service_name, 405 single_resource_json_definition=resource_model['service'], 406 service_context=service_context 407 ) 408 409 return cls(client=client) 410 411 def _register_default_handlers(self): 412 413 # S3 customizations 414 self._session.register( 415 'creating-client-class.s3', 416 boto3.utils.lazy_call( 417 'boto3.s3.inject.inject_s3_transfer_methods')) 418 self._session.register( 419 'creating-resource-class.s3.Bucket', 420 boto3.utils.lazy_call( 421 'boto3.s3.inject.inject_bucket_methods')) 422 self._session.register( 423 'creating-resource-class.s3.Object', 424 boto3.utils.lazy_call( 425 'boto3.s3.inject.inject_object_methods')) 426 self._session.register( 427 'creating-resource-class.s3.ObjectSummary', 428 boto3.utils.lazy_call( 429 'boto3.s3.inject.inject_object_summary_methods')) 430 431 # DynamoDb customizations 432 self._session.register( 433 'creating-resource-class.dynamodb', 434 boto3.utils.lazy_call( 435 'boto3.dynamodb.transform.register_high_level_interface'), 436 unique_id='high-level-dynamodb') 437 self._session.register( 438 'creating-resource-class.dynamodb.Table', 439 boto3.utils.lazy_call( 440 'boto3.dynamodb.table.register_table_methods'), 441 unique_id='high-level-dynamodb-table') 442 443 # EC2 Customizations 444 self._session.register( 445 'creating-resource-class.ec2.ServiceResource', 446 boto3.utils.lazy_call( 447 'boto3.ec2.createtags.inject_create_tags')) 448 449 self._session.register( 450 'creating-resource-class.ec2.Instance', 451 boto3.utils.lazy_call( 452 'boto3.ec2.deletetags.inject_delete_tags', 453 event_emitter=self.events)) 454