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.
1270 lines
48 KiB
1270 lines
48 KiB
# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
|
|
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
# may not use this file except in compliance with the License. A copy of
|
|
# the License is located at
|
|
#
|
|
# http://aws.amazon.com/apache2.0/
|
|
#
|
|
# or in the "license" file accompanying this file. This file is
|
|
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
# ANY KIND, either express or implied. See the License for the specific
|
|
# language governing permissions and limitations under the License.
|
|
"""
|
|
This module contains the main interface to the botocore package, the
|
|
Session object.
|
|
"""
|
|
|
|
import copy
|
|
import logging
|
|
import os
|
|
import platform
|
|
import socket
|
|
import warnings
|
|
|
|
import botocore.client
|
|
import botocore.configloader
|
|
import botocore.credentials
|
|
import botocore.tokens
|
|
from botocore import (
|
|
UNSIGNED,
|
|
__version__,
|
|
handlers,
|
|
invoke_initializers,
|
|
monitoring,
|
|
paginate,
|
|
retryhandler,
|
|
translate,
|
|
waiter,
|
|
)
|
|
from botocore.compat import HAS_CRT, MutableMapping
|
|
from botocore.configprovider import (
|
|
BOTOCORE_DEFAUT_SESSION_VARIABLES,
|
|
ConfigChainFactory,
|
|
ConfiguredEndpointProvider,
|
|
ConfigValueStore,
|
|
DefaultConfigResolver,
|
|
SmartDefaultsConfigStoreFactory,
|
|
create_botocore_default_config_mapping,
|
|
)
|
|
from botocore.errorfactory import ClientExceptionsFactory
|
|
from botocore.exceptions import (
|
|
ConfigNotFound,
|
|
InvalidDefaultsMode,
|
|
PartialCredentialsError,
|
|
ProfileNotFound,
|
|
UnknownServiceError,
|
|
)
|
|
from botocore.hooks import (
|
|
EventAliaser,
|
|
HierarchicalEmitter,
|
|
first_non_none_response,
|
|
)
|
|
from botocore.loaders import create_loader
|
|
from botocore.model import ServiceModel
|
|
from botocore.parsers import ResponseParserFactory
|
|
from botocore.regions import EndpointResolver
|
|
from botocore.useragent import UserAgentString
|
|
from botocore.utils import (
|
|
EVENT_ALIASES,
|
|
IMDSRegionProvider,
|
|
validate_region_name,
|
|
)
|
|
|
|
from botocore.compat import HAS_CRT # noqa
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Session:
|
|
"""
|
|
The Session object collects together useful functionality
|
|
from `botocore` as well as important data such as configuration
|
|
information and credentials into a single, easy-to-use object.
|
|
|
|
:ivar available_profiles: A list of profiles defined in the config
|
|
file associated with this session.
|
|
:ivar profile: The current profile.
|
|
"""
|
|
|
|
SESSION_VARIABLES = copy.copy(BOTOCORE_DEFAUT_SESSION_VARIABLES)
|
|
|
|
#: The default format string to use when configuring the botocore logger.
|
|
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
|
|
def __init__(
|
|
self,
|
|
session_vars=None,
|
|
event_hooks=None,
|
|
include_builtin_handlers=True,
|
|
profile=None,
|
|
):
|
|
"""
|
|
Create a new Session object.
|
|
|
|
:type session_vars: dict
|
|
:param session_vars: A dictionary that is used to override some or all
|
|
of the environment variables associated with this session. The
|
|
key/value pairs defined in this dictionary will override the
|
|
corresponding variables defined in ``SESSION_VARIABLES``.
|
|
|
|
:type event_hooks: BaseEventHooks
|
|
:param event_hooks: The event hooks object to use. If one is not
|
|
provided, an event hooks object will be automatically created
|
|
for you.
|
|
|
|
:type include_builtin_handlers: bool
|
|
:param include_builtin_handlers: Indicates whether or not to
|
|
automatically register builtin handlers.
|
|
|
|
:type profile: str
|
|
:param profile: The name of the profile to use for this
|
|
session. Note that the profile can only be set when
|
|
the session is created.
|
|
|
|
"""
|
|
if event_hooks is None:
|
|
self._original_handler = HierarchicalEmitter()
|
|
else:
|
|
self._original_handler = event_hooks
|
|
self._events = EventAliaser(self._original_handler)
|
|
if include_builtin_handlers:
|
|
self._register_builtin_handlers(self._events)
|
|
self.user_agent_name = 'Botocore'
|
|
self.user_agent_version = __version__
|
|
self.user_agent_extra = ''
|
|
# The _profile attribute is just used to cache the value
|
|
# of the current profile to avoid going through the normal
|
|
# config lookup process each access time.
|
|
self._profile = None
|
|
self._config = None
|
|
self._credentials = None
|
|
self._auth_token = None
|
|
self._profile_map = None
|
|
# This is a dict that stores per session specific config variable
|
|
# overrides via set_config_variable().
|
|
self._session_instance_vars = {}
|
|
if profile is not None:
|
|
self._session_instance_vars['profile'] = profile
|
|
self._client_config = None
|
|
self._last_client_region_used = None
|
|
self._components = ComponentLocator()
|
|
self._internal_components = ComponentLocator()
|
|
self._register_components()
|
|
self.session_var_map = SessionVarDict(self, self.SESSION_VARIABLES)
|
|
if session_vars is not None:
|
|
self.session_var_map.update(session_vars)
|
|
invoke_initializers(self)
|
|
|
|
def _register_components(self):
|
|
self._register_credential_provider()
|
|
self._register_token_provider()
|
|
self._register_data_loader()
|
|
self._register_endpoint_resolver()
|
|
self._register_event_emitter()
|
|
self._register_response_parser_factory()
|
|
self._register_exceptions_factory()
|
|
self._register_config_store()
|
|
self._register_monitor()
|
|
self._register_default_config_resolver()
|
|
self._register_smart_defaults_factory()
|
|
self._register_user_agent_creator()
|
|
|
|
def _register_event_emitter(self):
|
|
self._components.register_component('event_emitter', self._events)
|
|
|
|
def _register_token_provider(self):
|
|
self._components.lazy_register_component(
|
|
'token_provider', self._create_token_resolver
|
|
)
|
|
|
|
def _create_token_resolver(self):
|
|
return botocore.tokens.create_token_resolver(self)
|
|
|
|
def _register_credential_provider(self):
|
|
self._components.lazy_register_component(
|
|
'credential_provider', self._create_credential_resolver
|
|
)
|
|
|
|
def _create_credential_resolver(self):
|
|
return botocore.credentials.create_credential_resolver(
|
|
self, region_name=self._last_client_region_used
|
|
)
|
|
|
|
def _register_data_loader(self):
|
|
self._components.lazy_register_component(
|
|
'data_loader',
|
|
lambda: create_loader(self.get_config_variable('data_path')),
|
|
)
|
|
|
|
def _register_endpoint_resolver(self):
|
|
def create_default_resolver():
|
|
loader = self.get_component('data_loader')
|
|
endpoints, path = loader.load_data_with_path('endpoints')
|
|
uses_builtin = loader.is_builtin_path(path)
|
|
return EndpointResolver(endpoints, uses_builtin_data=uses_builtin)
|
|
|
|
self._internal_components.lazy_register_component(
|
|
'endpoint_resolver', create_default_resolver
|
|
)
|
|
|
|
def _register_default_config_resolver(self):
|
|
def create_default_config_resolver():
|
|
loader = self.get_component('data_loader')
|
|
defaults = loader.load_data('sdk-default-configuration')
|
|
return DefaultConfigResolver(defaults)
|
|
|
|
self._internal_components.lazy_register_component(
|
|
'default_config_resolver', create_default_config_resolver
|
|
)
|
|
|
|
def _register_smart_defaults_factory(self):
|
|
def create_smart_defaults_factory():
|
|
default_config_resolver = self._get_internal_component(
|
|
'default_config_resolver'
|
|
)
|
|
imds_region_provider = IMDSRegionProvider(session=self)
|
|
return SmartDefaultsConfigStoreFactory(
|
|
default_config_resolver, imds_region_provider
|
|
)
|
|
|
|
self._internal_components.lazy_register_component(
|
|
'smart_defaults_factory', create_smart_defaults_factory
|
|
)
|
|
|
|
def _register_response_parser_factory(self):
|
|
self._components.register_component(
|
|
'response_parser_factory', ResponseParserFactory()
|
|
)
|
|
|
|
def _register_exceptions_factory(self):
|
|
self._internal_components.register_component(
|
|
'exceptions_factory', ClientExceptionsFactory()
|
|
)
|
|
|
|
def _register_builtin_handlers(self, events):
|
|
for spec in handlers.BUILTIN_HANDLERS:
|
|
if len(spec) == 2:
|
|
event_name, handler = spec
|
|
self.register(event_name, handler)
|
|
else:
|
|
event_name, handler, register_type = spec
|
|
if register_type is handlers.REGISTER_FIRST:
|
|
self._events.register_first(event_name, handler)
|
|
elif register_type is handlers.REGISTER_LAST:
|
|
self._events.register_last(event_name, handler)
|
|
|
|
def _register_config_store(self):
|
|
config_store_component = ConfigValueStore(
|
|
mapping=create_botocore_default_config_mapping(self)
|
|
)
|
|
self._components.register_component(
|
|
'config_store', config_store_component
|
|
)
|
|
|
|
def _register_monitor(self):
|
|
self._internal_components.lazy_register_component(
|
|
'monitor', self._create_csm_monitor
|
|
)
|
|
|
|
def _register_user_agent_creator(self):
|
|
uas = UserAgentString.from_environment()
|
|
self._components.register_component('user_agent_creator', uas)
|
|
|
|
def _create_csm_monitor(self):
|
|
if self.get_config_variable('csm_enabled'):
|
|
client_id = self.get_config_variable('csm_client_id')
|
|
host = self.get_config_variable('csm_host')
|
|
port = self.get_config_variable('csm_port')
|
|
handler = monitoring.Monitor(
|
|
adapter=monitoring.MonitorEventAdapter(),
|
|
publisher=monitoring.SocketPublisher(
|
|
socket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
|
|
host=host,
|
|
port=port,
|
|
serializer=monitoring.CSMSerializer(
|
|
csm_client_id=client_id
|
|
),
|
|
),
|
|
)
|
|
return handler
|
|
return None
|
|
|
|
def _get_crt_version(self):
|
|
user_agent_creator = self.get_component('user_agent_creator')
|
|
return user_agent_creator._crt_version or 'Unknown'
|
|
|
|
@property
|
|
def available_profiles(self):
|
|
return list(self._build_profile_map().keys())
|
|
|
|
def _build_profile_map(self):
|
|
# This will build the profile map if it has not been created,
|
|
# otherwise it will return the cached value. The profile map
|
|
# is a list of profile names, to the config values for the profile.
|
|
if self._profile_map is None:
|
|
self._profile_map = self.full_config['profiles']
|
|
return self._profile_map
|
|
|
|
@property
|
|
def profile(self):
|
|
if self._profile is None:
|
|
profile = self.get_config_variable('profile')
|
|
self._profile = profile
|
|
return self._profile
|
|
|
|
def get_config_variable(self, logical_name, methods=None):
|
|
if methods is not None:
|
|
return self._get_config_variable_with_custom_methods(
|
|
logical_name, methods
|
|
)
|
|
return self.get_component('config_store').get_config_variable(
|
|
logical_name
|
|
)
|
|
|
|
def _get_config_variable_with_custom_methods(self, logical_name, methods):
|
|
# If a custom list of methods was supplied we need to perserve the
|
|
# behavior with the new system. To do so a new chain that is a copy of
|
|
# the old one will be constructed, but only with the supplied methods
|
|
# being added to the chain. This chain will be consulted for a value
|
|
# and then thrown out. This is not efficient, nor is the methods arg
|
|
# used in botocore, this is just for backwards compatibility.
|
|
chain_builder = SubsetChainConfigFactory(session=self, methods=methods)
|
|
mapping = create_botocore_default_config_mapping(self)
|
|
for name, config_options in self.session_var_map.items():
|
|
config_name, env_vars, default, typecast = config_options
|
|
build_chain_config_args = {
|
|
'conversion_func': typecast,
|
|
'default': default,
|
|
}
|
|
if 'instance' in methods:
|
|
build_chain_config_args['instance_name'] = name
|
|
if 'env' in methods:
|
|
build_chain_config_args['env_var_names'] = env_vars
|
|
if 'config' in methods:
|
|
build_chain_config_args['config_property_name'] = config_name
|
|
mapping[name] = chain_builder.create_config_chain(
|
|
**build_chain_config_args
|
|
)
|
|
config_store_component = ConfigValueStore(mapping=mapping)
|
|
value = config_store_component.get_config_variable(logical_name)
|
|
return value
|
|
|
|
def set_config_variable(self, logical_name, value):
|
|
"""Set a configuration variable to a specific value.
|
|
|
|
By using this method, you can override the normal lookup
|
|
process used in ``get_config_variable`` by explicitly setting
|
|
a value. Subsequent calls to ``get_config_variable`` will
|
|
use the ``value``. This gives you per-session specific
|
|
configuration values.
|
|
|
|
::
|
|
>>> # Assume logical name 'foo' maps to env var 'FOO'
|
|
>>> os.environ['FOO'] = 'myvalue'
|
|
>>> s.get_config_variable('foo')
|
|
'myvalue'
|
|
>>> s.set_config_variable('foo', 'othervalue')
|
|
>>> s.get_config_variable('foo')
|
|
'othervalue'
|
|
|
|
:type logical_name: str
|
|
:param logical_name: The logical name of the session variable
|
|
you want to set. These are the keys in ``SESSION_VARIABLES``.
|
|
:param value: The value to associate with the config variable.
|
|
|
|
"""
|
|
logger.debug(
|
|
"Setting config variable for %s to %r",
|
|
logical_name,
|
|
value,
|
|
)
|
|
self._session_instance_vars[logical_name] = value
|
|
|
|
def instance_variables(self):
|
|
return copy.copy(self._session_instance_vars)
|
|
|
|
def get_scoped_config(self):
|
|
"""
|
|
Returns the config values from the config file scoped to the current
|
|
profile.
|
|
|
|
The configuration data is loaded **only** from the config file.
|
|
It does not resolve variables based on different locations
|
|
(e.g. first from the session instance, then from environment
|
|
variables, then from the config file). If you want this lookup
|
|
behavior, use the ``get_config_variable`` method instead.
|
|
|
|
Note that this configuration is specific to a single profile (the
|
|
``profile`` session variable).
|
|
|
|
If the ``profile`` session variable is set and the profile does
|
|
not exist in the config file, a ``ProfileNotFound`` exception
|
|
will be raised.
|
|
|
|
:raises: ConfigNotFound, ConfigParseError, ProfileNotFound
|
|
:rtype: dict
|
|
|
|
"""
|
|
profile_name = self.get_config_variable('profile')
|
|
profile_map = self._build_profile_map()
|
|
# If a profile is not explicitly set return the default
|
|
# profile config or an empty config dict if we don't have
|
|
# a default profile.
|
|
if profile_name is None:
|
|
return profile_map.get('default', {})
|
|
elif profile_name not in profile_map:
|
|
# Otherwise if they specified a profile, it has to
|
|
# exist (even if it's the default profile) otherwise
|
|
# we complain.
|
|
raise ProfileNotFound(profile=profile_name)
|
|
else:
|
|
return profile_map[profile_name]
|
|
|
|
@property
|
|
def full_config(self):
|
|
"""Return the parsed config file.
|
|
|
|
The ``get_config`` method returns the config associated with the
|
|
specified profile. This property returns the contents of the
|
|
**entire** config file.
|
|
|
|
:rtype: dict
|
|
"""
|
|
if self._config is None:
|
|
try:
|
|
config_file = self.get_config_variable('config_file')
|
|
self._config = botocore.configloader.load_config(config_file)
|
|
except ConfigNotFound:
|
|
self._config = {'profiles': {}}
|
|
try:
|
|
# Now we need to inject the profiles from the
|
|
# credentials file. We don't actually need the values
|
|
# in the creds file, only the profile names so that we
|
|
# can validate the user is not referring to a nonexistent
|
|
# profile.
|
|
cred_file = self.get_config_variable('credentials_file')
|
|
cred_profiles = botocore.configloader.raw_config_parse(
|
|
cred_file
|
|
)
|
|
for profile in cred_profiles:
|
|
cred_vars = cred_profiles[profile]
|
|
if profile not in self._config['profiles']:
|
|
self._config['profiles'][profile] = cred_vars
|
|
else:
|
|
self._config['profiles'][profile].update(cred_vars)
|
|
except ConfigNotFound:
|
|
pass
|
|
return self._config
|
|
|
|
def get_default_client_config(self):
|
|
"""Retrieves the default config for creating clients
|
|
|
|
:rtype: botocore.client.Config
|
|
:returns: The default client config object when creating clients. If
|
|
the value is ``None`` then there is no default config object
|
|
attached to the session.
|
|
"""
|
|
return self._client_config
|
|
|
|
def set_default_client_config(self, client_config):
|
|
"""Sets the default config for creating clients
|
|
|
|
:type client_config: botocore.client.Config
|
|
:param client_config: The default client config object when creating
|
|
clients. If the value is ``None`` then there is no default config
|
|
object attached to the session.
|
|
"""
|
|
self._client_config = client_config
|
|
|
|
def set_credentials(self, access_key, secret_key, token=None):
|
|
"""
|
|
Manually create credentials for this session. If you would
|
|
prefer to use botocore without a config file, environment variables,
|
|
or IAM roles, you can pass explicit credentials into this
|
|
method to establish credentials for this session.
|
|
|
|
:type access_key: str
|
|
:param access_key: The access key part of the credentials.
|
|
|
|
:type secret_key: str
|
|
:param secret_key: The secret key part of the credentials.
|
|
|
|
:type token: str
|
|
:param token: An option session token used by STS session
|
|
credentials.
|
|
"""
|
|
self._credentials = botocore.credentials.Credentials(
|
|
access_key, secret_key, token
|
|
)
|
|
|
|
def get_credentials(self):
|
|
"""
|
|
Return the :class:`botocore.credential.Credential` object
|
|
associated with this session. If the credentials have not
|
|
yet been loaded, this will attempt to load them. If they
|
|
have already been loaded, this will return the cached
|
|
credentials.
|
|
|
|
"""
|
|
if self._credentials is None:
|
|
self._credentials = self._components.get_component(
|
|
'credential_provider'
|
|
).load_credentials()
|
|
return self._credentials
|
|
|
|
def get_auth_token(self):
|
|
"""
|
|
Return the :class:`botocore.tokens.AuthToken` object associated with
|
|
this session. If the authorization token has not yet been loaded, this
|
|
will attempt to load it. If it has already been loaded, this will
|
|
return the cached authorization token.
|
|
|
|
"""
|
|
if self._auth_token is None:
|
|
provider = self._components.get_component('token_provider')
|
|
self._auth_token = provider.load_token()
|
|
return self._auth_token
|
|
|
|
def user_agent(self):
|
|
"""
|
|
Return a string suitable for use as a User-Agent header.
|
|
The string will be of the form:
|
|
|
|
<agent_name>/<agent_version> Python/<py_ver> <plat_name>/<plat_ver> <exec_env>
|
|
|
|
Where:
|
|
|
|
- agent_name is the value of the `user_agent_name` attribute
|
|
of the session object (`Botocore` by default).
|
|
- agent_version is the value of the `user_agent_version`
|
|
attribute of the session object (the botocore version by default).
|
|
by default.
|
|
- py_ver is the version of the Python interpreter beng used.
|
|
- plat_name is the name of the platform (e.g. Darwin)
|
|
- plat_ver is the version of the platform
|
|
- exec_env is exec-env/$AWS_EXECUTION_ENV
|
|
|
|
If ``user_agent_extra`` is not empty, then this value will be
|
|
appended to the end of the user agent string.
|
|
|
|
"""
|
|
base = (
|
|
f'{self.user_agent_name}/{self.user_agent_version} '
|
|
f'Python/{platform.python_version()} '
|
|
f'{platform.system()}/{platform.release()}'
|
|
)
|
|
if HAS_CRT:
|
|
base += ' awscrt/%s' % self._get_crt_version()
|
|
if os.environ.get('AWS_EXECUTION_ENV') is not None:
|
|
base += ' exec-env/%s' % os.environ.get('AWS_EXECUTION_ENV')
|
|
if self.user_agent_extra:
|
|
base += ' %s' % self.user_agent_extra
|
|
|
|
return base
|
|
|
|
def get_data(self, data_path):
|
|
"""
|
|
Retrieve the data associated with `data_path`.
|
|
|
|
:type data_path: str
|
|
:param data_path: The path to the data you wish to retrieve.
|
|
"""
|
|
return self.get_component('data_loader').load_data(data_path)
|
|
|
|
def get_service_model(self, service_name, api_version=None):
|
|
"""Get the service model object.
|
|
|
|
:type service_name: string
|
|
:param service_name: The service name
|
|
|
|
:type api_version: string
|
|
:param api_version: The API version of the service. If none is
|
|
provided, then the latest API version will be used.
|
|
|
|
:rtype: L{botocore.model.ServiceModel}
|
|
:return: The botocore service model for the service.
|
|
|
|
"""
|
|
service_description = self.get_service_data(service_name, api_version)
|
|
return ServiceModel(service_description, service_name=service_name)
|
|
|
|
def get_waiter_model(self, service_name, api_version=None):
|
|
loader = self.get_component('data_loader')
|
|
waiter_config = loader.load_service_model(
|
|
service_name, 'waiters-2', api_version
|
|
)
|
|
return waiter.WaiterModel(waiter_config)
|
|
|
|
def get_paginator_model(self, service_name, api_version=None):
|
|
loader = self.get_component('data_loader')
|
|
paginator_config = loader.load_service_model(
|
|
service_name, 'paginators-1', api_version
|
|
)
|
|
return paginate.PaginatorModel(paginator_config)
|
|
|
|
def get_service_data(self, service_name, api_version=None):
|
|
"""
|
|
Retrieve the fully merged data associated with a service.
|
|
"""
|
|
data_path = service_name
|
|
service_data = self.get_component('data_loader').load_service_model(
|
|
data_path, type_name='service-2', api_version=api_version
|
|
)
|
|
service_id = EVENT_ALIASES.get(service_name, service_name)
|
|
self._events.emit(
|
|
'service-data-loaded.%s' % service_id,
|
|
service_data=service_data,
|
|
service_name=service_name,
|
|
session=self,
|
|
)
|
|
return service_data
|
|
|
|
def get_available_services(self):
|
|
"""
|
|
Return a list of names of available services.
|
|
"""
|
|
return self.get_component('data_loader').list_available_services(
|
|
type_name='service-2'
|
|
)
|
|
|
|
def set_debug_logger(self, logger_name='botocore'):
|
|
"""
|
|
Convenience function to quickly configure full debug output
|
|
to go to the console.
|
|
"""
|
|
self.set_stream_logger(logger_name, logging.DEBUG)
|
|
|
|
def set_stream_logger(
|
|
self, logger_name, log_level, stream=None, format_string=None
|
|
):
|
|
"""
|
|
Convenience method to configure a stream logger.
|
|
|
|
:type logger_name: str
|
|
:param logger_name: The name of the logger to configure
|
|
|
|
:type log_level: str
|
|
:param log_level: The log level to set for the logger. This
|
|
is any param supported by the ``.setLevel()`` method of
|
|
a ``Log`` object.
|
|
|
|
:type stream: file
|
|
:param stream: A file like object to log to. If none is provided
|
|
then sys.stderr will be used.
|
|
|
|
:type format_string: str
|
|
:param format_string: The format string to use for the log
|
|
formatter. If none is provided this will default to
|
|
``self.LOG_FORMAT``.
|
|
|
|
"""
|
|
log = logging.getLogger(logger_name)
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
ch = logging.StreamHandler(stream)
|
|
ch.setLevel(log_level)
|
|
|
|
# create formatter
|
|
if format_string is None:
|
|
format_string = self.LOG_FORMAT
|
|
formatter = logging.Formatter(format_string)
|
|
|
|
# add formatter to ch
|
|
ch.setFormatter(formatter)
|
|
|
|
# add ch to logger
|
|
log.addHandler(ch)
|
|
|
|
def set_file_logger(self, log_level, path, logger_name='botocore'):
|
|
"""
|
|
Convenience function to quickly configure any level of logging
|
|
to a file.
|
|
|
|
:type log_level: int
|
|
:param log_level: A log level as specified in the `logging` module
|
|
|
|
:type path: string
|
|
:param path: Path to the log file. The file will be created
|
|
if it doesn't already exist.
|
|
"""
|
|
log = logging.getLogger(logger_name)
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
# create console handler and set level to debug
|
|
ch = logging.FileHandler(path)
|
|
ch.setLevel(log_level)
|
|
|
|
# create formatter
|
|
formatter = logging.Formatter(self.LOG_FORMAT)
|
|
|
|
# add formatter to ch
|
|
ch.setFormatter(formatter)
|
|
|
|
# add ch to logger
|
|
log.addHandler(ch)
|
|
|
|
def register(
|
|
self, event_name, handler, unique_id=None, unique_id_uses_count=False
|
|
):
|
|
"""Register a handler with an event.
|
|
|
|
:type event_name: str
|
|
:param event_name: The name of the event.
|
|
|
|
:type handler: callable
|
|
:param handler: The callback to invoke when the event
|
|
is emitted. This object must be callable, and must
|
|
accept ``**kwargs``. If either of these preconditions are
|
|
not met, a ``ValueError`` will be raised.
|
|
|
|
:type unique_id: str
|
|
:param unique_id: An optional identifier to associate with the
|
|
registration. A unique_id can only be used once for
|
|
the entire session registration (unless it is unregistered).
|
|
This can be used to prevent an event handler from being
|
|
registered twice.
|
|
|
|
:param unique_id_uses_count: boolean
|
|
:param unique_id_uses_count: Specifies if the event should maintain
|
|
a count when a ``unique_id`` is registered and unregisted. The
|
|
event can only be completely unregistered once every register call
|
|
using the unique id has been matched by an ``unregister`` call.
|
|
If ``unique_id`` is specified, subsequent ``register``
|
|
calls must use the same value for ``unique_id_uses_count``
|
|
as the ``register`` call that first registered the event.
|
|
|
|
:raises ValueError: If the call to ``register`` uses ``unique_id``
|
|
but the value for ``unique_id_uses_count`` differs from the
|
|
``unique_id_uses_count`` value declared by the very first
|
|
``register`` call for that ``unique_id``.
|
|
"""
|
|
self._events.register(
|
|
event_name,
|
|
handler,
|
|
unique_id,
|
|
unique_id_uses_count=unique_id_uses_count,
|
|
)
|
|
|
|
def unregister(
|
|
self,
|
|
event_name,
|
|
handler=None,
|
|
unique_id=None,
|
|
unique_id_uses_count=False,
|
|
):
|
|
"""Unregister a handler with an event.
|
|
|
|
:type event_name: str
|
|
:param event_name: The name of the event.
|
|
|
|
:type handler: callable
|
|
:param handler: The callback to unregister.
|
|
|
|
:type unique_id: str
|
|
:param unique_id: A unique identifier identifying the callback
|
|
to unregister. You can provide either the handler or the
|
|
unique_id, you do not have to provide both.
|
|
|
|
:param unique_id_uses_count: boolean
|
|
:param unique_id_uses_count: Specifies if the event should maintain
|
|
a count when a ``unique_id`` is registered and unregisted. The
|
|
event can only be completely unregistered once every ``register``
|
|
call using the ``unique_id`` has been matched by an ``unregister``
|
|
call. If the ``unique_id`` is specified, subsequent
|
|
``unregister`` calls must use the same value for
|
|
``unique_id_uses_count`` as the ``register`` call that first
|
|
registered the event.
|
|
|
|
:raises ValueError: If the call to ``unregister`` uses ``unique_id``
|
|
but the value for ``unique_id_uses_count`` differs from the
|
|
``unique_id_uses_count`` value declared by the very first
|
|
``register`` call for that ``unique_id``.
|
|
"""
|
|
self._events.unregister(
|
|
event_name,
|
|
handler=handler,
|
|
unique_id=unique_id,
|
|
unique_id_uses_count=unique_id_uses_count,
|
|
)
|
|
|
|
def emit(self, event_name, **kwargs):
|
|
return self._events.emit(event_name, **kwargs)
|
|
|
|
def emit_first_non_none_response(self, event_name, **kwargs):
|
|
responses = self._events.emit(event_name, **kwargs)
|
|
return first_non_none_response(responses)
|
|
|
|
def get_component(self, name):
|
|
try:
|
|
return self._components.get_component(name)
|
|
except ValueError:
|
|
if name in ['endpoint_resolver', 'exceptions_factory']:
|
|
warnings.warn(
|
|
'Fetching the %s component with the get_component() '
|
|
'method is deprecated as the component has always been '
|
|
'considered an internal interface of botocore' % name,
|
|
DeprecationWarning,
|
|
)
|
|
return self._internal_components.get_component(name)
|
|
raise
|
|
|
|
def _get_internal_component(self, name):
|
|
# While this method may be called by botocore classes outside of the
|
|
# Session, this method should **never** be used by a class that lives
|
|
# outside of botocore.
|
|
return self._internal_components.get_component(name)
|
|
|
|
def _register_internal_component(self, name, component):
|
|
# While this method may be called by botocore classes outside of the
|
|
# Session, this method should **never** be used by a class that lives
|
|
# outside of botocore.
|
|
return self._internal_components.register_component(name, component)
|
|
|
|
def register_component(self, name, component):
|
|
self._components.register_component(name, component)
|
|
|
|
def lazy_register_component(self, name, component):
|
|
self._components.lazy_register_component(name, component)
|
|
|
|
def create_client(
|
|
self,
|
|
service_name,
|
|
region_name=None,
|
|
api_version=None,
|
|
use_ssl=True,
|
|
verify=None,
|
|
endpoint_url=None,
|
|
aws_access_key_id=None,
|
|
aws_secret_access_key=None,
|
|
aws_session_token=None,
|
|
config=None,
|
|
):
|
|
"""Create a botocore client.
|
|
|
|
:type service_name: string
|
|
:param service_name: The name of the service for which a client will
|
|
be created. You can use the ``Session.get_available_services()``
|
|
method to get a list of all available service names.
|
|
|
|
:type region_name: string
|
|
:param region_name: The name of the region associated with the client.
|
|
A client is associated with a single region.
|
|
|
|
:type api_version: string
|
|
:param api_version: The API version to use. By default, botocore will
|
|
use the latest API version when creating a client. You only need
|
|
to specify this parameter if you want to use a previous API version
|
|
of the client.
|
|
|
|
:type use_ssl: boolean
|
|
:param use_ssl: Whether or not to use SSL. By default, SSL is used.
|
|
Note that not all services support non-ssl connections.
|
|
|
|
:type verify: boolean/string
|
|
:param verify: Whether or not to verify SSL certificates.
|
|
By default SSL certificates are verified. You can provide the
|
|
following values:
|
|
|
|
* False - do not validate SSL certificates. SSL will still be
|
|
used (unless use_ssl is False), but SSL certificates
|
|
will not be verified.
|
|
* path/to/cert/bundle.pem - A filename of the CA cert bundle to
|
|
uses. You can specify this argument if you want to use a
|
|
different CA cert bundle than the one used by botocore.
|
|
|
|
:type endpoint_url: string
|
|
:param endpoint_url: The complete URL to use for the constructed
|
|
client. Normally, botocore will automatically construct the
|
|
appropriate URL to use when communicating with a service. You can
|
|
specify a complete URL (including the "http/https" scheme) to
|
|
override this behavior. If this value is provided, then
|
|
``use_ssl`` is ignored.
|
|
|
|
:type aws_access_key_id: string
|
|
:param aws_access_key_id: The access key to use when creating
|
|
the client. This is entirely optional, and if not provided,
|
|
the credentials configured for the session will automatically
|
|
be used. You only need to provide this argument if you want
|
|
to override the credentials used for this specific client.
|
|
|
|
:type aws_secret_access_key: string
|
|
:param aws_secret_access_key: The secret key to use when creating
|
|
the client. Same semantics as aws_access_key_id above.
|
|
|
|
:type aws_session_token: string
|
|
:param aws_session_token: The session token to use when creating
|
|
the client. Same semantics as aws_access_key_id above.
|
|
|
|
:type config: botocore.client.Config
|
|
:param config: Advanced client configuration options. If a value
|
|
is specified in the client config, its value will take precedence
|
|
over environment variables and configuration values, but not over
|
|
a value passed explicitly to the method. If a default config
|
|
object is set on the session, the config object used when creating
|
|
the client will be the result of calling ``merge()`` on the
|
|
default config with the config provided to this call.
|
|
|
|
:rtype: botocore.client.BaseClient
|
|
:return: A botocore client instance
|
|
|
|
"""
|
|
default_client_config = self.get_default_client_config()
|
|
# If a config is provided and a default config is set, then
|
|
# use the config resulting from merging the two.
|
|
if config is not None and default_client_config is not None:
|
|
config = default_client_config.merge(config)
|
|
# If a config was not provided then use the default
|
|
# client config from the session
|
|
elif default_client_config is not None:
|
|
config = default_client_config
|
|
|
|
region_name = self._resolve_region_name(region_name, config)
|
|
|
|
# Figure out the verify value base on the various
|
|
# configuration options.
|
|
if verify is None:
|
|
verify = self.get_config_variable('ca_bundle')
|
|
|
|
if api_version is None:
|
|
api_version = self.get_config_variable('api_versions').get(
|
|
service_name, None
|
|
)
|
|
|
|
loader = self.get_component('data_loader')
|
|
event_emitter = self.get_component('event_emitter')
|
|
response_parser_factory = self.get_component('response_parser_factory')
|
|
if config is not None and config.signature_version is UNSIGNED:
|
|
credentials = None
|
|
elif (
|
|
aws_access_key_id is not None and aws_secret_access_key is not None
|
|
):
|
|
credentials = botocore.credentials.Credentials(
|
|
access_key=aws_access_key_id,
|
|
secret_key=aws_secret_access_key,
|
|
token=aws_session_token,
|
|
)
|
|
elif self._missing_cred_vars(aws_access_key_id, aws_secret_access_key):
|
|
raise PartialCredentialsError(
|
|
provider='explicit',
|
|
cred_var=self._missing_cred_vars(
|
|
aws_access_key_id, aws_secret_access_key
|
|
),
|
|
)
|
|
else:
|
|
credentials = self.get_credentials()
|
|
auth_token = self.get_auth_token()
|
|
endpoint_resolver = self._get_internal_component('endpoint_resolver')
|
|
exceptions_factory = self._get_internal_component('exceptions_factory')
|
|
config_store = copy.copy(self.get_component('config_store'))
|
|
user_agent_creator = self.get_component('user_agent_creator')
|
|
# Session configuration values for the user agent string are applied
|
|
# just before each client creation because they may have been modified
|
|
# at any time between session creation and client creation.
|
|
user_agent_creator.set_session_config(
|
|
session_user_agent_name=self.user_agent_name,
|
|
session_user_agent_version=self.user_agent_version,
|
|
session_user_agent_extra=self.user_agent_extra,
|
|
)
|
|
defaults_mode = self._resolve_defaults_mode(config, config_store)
|
|
if defaults_mode != 'legacy':
|
|
smart_defaults_factory = self._get_internal_component(
|
|
'smart_defaults_factory'
|
|
)
|
|
smart_defaults_factory.merge_smart_defaults(
|
|
config_store, defaults_mode, region_name
|
|
)
|
|
|
|
self._add_configured_endpoint_provider(
|
|
client_name=service_name,
|
|
config_store=config_store,
|
|
)
|
|
|
|
client_creator = botocore.client.ClientCreator(
|
|
loader,
|
|
endpoint_resolver,
|
|
self.user_agent(),
|
|
event_emitter,
|
|
retryhandler,
|
|
translate,
|
|
response_parser_factory,
|
|
exceptions_factory,
|
|
config_store,
|
|
user_agent_creator=user_agent_creator,
|
|
)
|
|
client = client_creator.create_client(
|
|
service_name=service_name,
|
|
region_name=region_name,
|
|
is_secure=use_ssl,
|
|
endpoint_url=endpoint_url,
|
|
verify=verify,
|
|
credentials=credentials,
|
|
scoped_config=self.get_scoped_config(),
|
|
client_config=config,
|
|
api_version=api_version,
|
|
auth_token=auth_token,
|
|
)
|
|
monitor = self._get_internal_component('monitor')
|
|
if monitor is not None:
|
|
monitor.register(client.meta.events)
|
|
return client
|
|
|
|
def _resolve_region_name(self, region_name, config):
|
|
# Figure out the user-provided region based on the various
|
|
# configuration options.
|
|
if region_name is None:
|
|
if config and config.region_name is not None:
|
|
region_name = config.region_name
|
|
else:
|
|
region_name = self.get_config_variable('region')
|
|
|
|
validate_region_name(region_name)
|
|
# For any client that we create in retrieving credentials
|
|
# we want to create it using the same region as specified in
|
|
# creating this client. It is important to note though that the
|
|
# credentials client is only created once per session. So if a new
|
|
# client is created with a different region, its credential resolver
|
|
# will use the region of the first client. However, that is not an
|
|
# issue as of now because the credential resolver uses only STS and
|
|
# the credentials returned at regional endpoints are valid across
|
|
# all regions in the partition.
|
|
self._last_client_region_used = region_name
|
|
return region_name
|
|
|
|
def _resolve_defaults_mode(self, client_config, config_store):
|
|
mode = config_store.get_config_variable('defaults_mode')
|
|
|
|
if client_config and client_config.defaults_mode:
|
|
mode = client_config.defaults_mode
|
|
|
|
default_config_resolver = self._get_internal_component(
|
|
'default_config_resolver'
|
|
)
|
|
default_modes = default_config_resolver.get_default_modes()
|
|
lmode = mode.lower()
|
|
if lmode not in default_modes:
|
|
raise InvalidDefaultsMode(
|
|
mode=mode, valid_modes=', '.join(default_modes)
|
|
)
|
|
|
|
return lmode
|
|
|
|
def _add_configured_endpoint_provider(self, client_name, config_store):
|
|
chain = ConfiguredEndpointProvider(
|
|
full_config=self.full_config,
|
|
scoped_config=self.get_scoped_config(),
|
|
client_name=client_name,
|
|
)
|
|
config_store.set_config_provider(
|
|
logical_name='endpoint_url',
|
|
provider=chain,
|
|
)
|
|
|
|
def _missing_cred_vars(self, access_key, secret_key):
|
|
if access_key is not None and secret_key is None:
|
|
return 'aws_secret_access_key'
|
|
if secret_key is not None and access_key is None:
|
|
return 'aws_access_key_id'
|
|
return None
|
|
|
|
def get_available_partitions(self):
|
|
"""Lists the available partitions found on disk
|
|
|
|
:rtype: list
|
|
:return: Returns a list of partition names (e.g., ["aws", "aws-cn"])
|
|
"""
|
|
resolver = self._get_internal_component('endpoint_resolver')
|
|
return resolver.get_available_partitions()
|
|
|
|
def get_partition_for_region(self, region_name):
|
|
"""Lists the partition name of a particular region.
|
|
|
|
:type region_name: string
|
|
:param region_name: Name of the region to list partition for (e.g.,
|
|
us-east-1).
|
|
|
|
:rtype: string
|
|
:return: Returns the respective partition name (e.g., aws).
|
|
"""
|
|
resolver = self._get_internal_component('endpoint_resolver')
|
|
return resolver.get_partition_for_region(region_name)
|
|
|
|
def get_available_regions(
|
|
self, service_name, partition_name='aws', allow_non_regional=False
|
|
):
|
|
"""Lists the region and endpoint names of a particular partition.
|
|
|
|
:type service_name: string
|
|
:param service_name: Name of a service to list endpoint for (e.g., s3).
|
|
This parameter accepts a service name (e.g., "elb") or endpoint
|
|
prefix (e.g., "elasticloadbalancing").
|
|
|
|
:type partition_name: string
|
|
:param partition_name: Name of the partition to limit endpoints to.
|
|
(e.g., aws for the public AWS endpoints, aws-cn for AWS China
|
|
endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.
|
|
|
|
:type allow_non_regional: bool
|
|
:param allow_non_regional: Set to True to include endpoints that are
|
|
not regional endpoints (e.g., s3-external-1,
|
|
fips-us-gov-west-1, etc).
|
|
:return: Returns a list of endpoint names (e.g., ["us-east-1"]).
|
|
"""
|
|
resolver = self._get_internal_component('endpoint_resolver')
|
|
results = []
|
|
try:
|
|
service_data = self.get_service_data(service_name)
|
|
endpoint_prefix = service_data['metadata'].get(
|
|
'endpointPrefix', service_name
|
|
)
|
|
results = resolver.get_available_endpoints(
|
|
endpoint_prefix, partition_name, allow_non_regional
|
|
)
|
|
except UnknownServiceError:
|
|
pass
|
|
return results
|
|
|
|
|
|
class ComponentLocator:
|
|
"""Service locator for session components."""
|
|
|
|
def __init__(self):
|
|
self._components = {}
|
|
self._deferred = {}
|
|
|
|
def get_component(self, name):
|
|
if name in self._deferred:
|
|
factory = self._deferred[name]
|
|
self._components[name] = factory()
|
|
# Only delete the component from the deferred dict after
|
|
# successfully creating the object from the factory as well as
|
|
# injecting the instantiated value into the _components dict.
|
|
try:
|
|
del self._deferred[name]
|
|
except KeyError:
|
|
# If we get here, it's likely that get_component was called
|
|
# concurrently from multiple threads, and another thread
|
|
# already deleted the entry. This means the factory was
|
|
# probably called twice, but cleaning up the deferred entry
|
|
# should not crash outright.
|
|
pass
|
|
try:
|
|
return self._components[name]
|
|
except KeyError:
|
|
raise ValueError("Unknown component: %s" % name)
|
|
|
|
def register_component(self, name, component):
|
|
self._components[name] = component
|
|
try:
|
|
del self._deferred[name]
|
|
except KeyError:
|
|
pass
|
|
|
|
def lazy_register_component(self, name, no_arg_factory):
|
|
self._deferred[name] = no_arg_factory
|
|
try:
|
|
del self._components[name]
|
|
except KeyError:
|
|
pass
|
|
|
|
|
|
class SessionVarDict(MutableMapping):
|
|
def __init__(self, session, session_vars):
|
|
self._session = session
|
|
self._store = copy.copy(session_vars)
|
|
|
|
def __getitem__(self, key):
|
|
return self._store[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
self._store[key] = value
|
|
self._update_config_store_from_session_vars(key, value)
|
|
|
|
def __delitem__(self, key):
|
|
del self._store[key]
|
|
|
|
def __iter__(self):
|
|
return iter(self._store)
|
|
|
|
def __len__(self):
|
|
return len(self._store)
|
|
|
|
def _update_config_store_from_session_vars(
|
|
self, logical_name, config_options
|
|
):
|
|
# This is for backwards compatibility. The new preferred way to
|
|
# modify configuration logic is to use the component system to get
|
|
# the config_store component from the session, and then update
|
|
# a key with a custom config provider(s).
|
|
# This backwards compatibility method takes the old session_vars
|
|
# list of tuples and and transforms that into a set of updates to
|
|
# the config_store component.
|
|
config_chain_builder = ConfigChainFactory(session=self._session)
|
|
config_name, env_vars, default, typecast = config_options
|
|
config_store = self._session.get_component('config_store')
|
|
config_store.set_config_provider(
|
|
logical_name,
|
|
config_chain_builder.create_config_chain(
|
|
instance_name=logical_name,
|
|
env_var_names=env_vars,
|
|
config_property_names=config_name,
|
|
default=default,
|
|
conversion_func=typecast,
|
|
),
|
|
)
|
|
|
|
|
|
class SubsetChainConfigFactory:
|
|
"""A class for creating backwards compatible configuration chains.
|
|
|
|
This class can be used instead of
|
|
:class:`botocore.configprovider.ConfigChainFactory` to make it honor the
|
|
methods argument to get_config_variable. This class can be used to filter
|
|
out providers that are not in the methods tuple when creating a new config
|
|
chain.
|
|
"""
|
|
|
|
def __init__(self, session, methods, environ=None):
|
|
self._factory = ConfigChainFactory(session, environ)
|
|
self._supported_methods = methods
|
|
|
|
def create_config_chain(
|
|
self,
|
|
instance_name=None,
|
|
env_var_names=None,
|
|
config_property_name=None,
|
|
default=None,
|
|
conversion_func=None,
|
|
):
|
|
"""Build a config chain following the standard botocore pattern.
|
|
|
|
This config chain factory will omit any providers not in the methods
|
|
tuple provided at initialization. For example if given the tuple
|
|
('instance', 'config',) it will not inject the environment provider
|
|
into the standard config chain. This lets the botocore session support
|
|
the custom ``methods`` argument for all the default botocore config
|
|
variables when calling ``get_config_variable``.
|
|
"""
|
|
if 'instance' not in self._supported_methods:
|
|
instance_name = None
|
|
if 'env' not in self._supported_methods:
|
|
env_var_names = None
|
|
if 'config' not in self._supported_methods:
|
|
config_property_name = None
|
|
return self._factory.create_config_chain(
|
|
instance_name=instance_name,
|
|
env_var_names=env_var_names,
|
|
config_property_names=config_property_name,
|
|
default=default,
|
|
conversion_func=conversion_func,
|
|
)
|
|
|
|
|
|
def get_session(env_vars=None):
|
|
"""
|
|
Return a new session object.
|
|
"""
|
|
return Session(env_vars)
|