You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

452 lines
19 KiB

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