Spaces:
Paused
Paused
| # Copyright 2018 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 interface for controlling how configuration | |
| is loaded. | |
| """ | |
| import copy | |
| import logging | |
| import os | |
| from botocore import utils | |
| from botocore.exceptions import InvalidConfigError | |
| logger = logging.getLogger(__name__) | |
| #: A default dictionary that maps the logical names for session variables | |
| #: to the specific environment variables and configuration file names | |
| #: that contain the values for these variables. | |
| #: When creating a new Session object, you can pass in your own dictionary | |
| #: to remap the logical names or to add new logical names. You can then | |
| #: get the current value for these variables by using the | |
| #: ``get_config_variable`` method of the :class:`botocore.session.Session` | |
| #: class. | |
| #: These form the keys of the dictionary. The values in the dictionary | |
| #: are tuples of (<config_name>, <environment variable>, <default value>, | |
| #: <conversion func>). | |
| #: The conversion func is a function that takes the configuration value | |
| #: as an argument and returns the converted value. If this value is | |
| #: None, then the configuration value is returned unmodified. This | |
| #: conversion function can be used to type convert config values to | |
| #: values other than the default values of strings. | |
| #: The ``profile`` and ``config_file`` variables should always have a | |
| #: None value for the first entry in the tuple because it doesn't make | |
| #: sense to look inside the config file for the location of the config | |
| #: file or for the default profile to use. | |
| #: The ``config_name`` is the name to look for in the configuration file, | |
| #: the ``env var`` is the OS environment variable (``os.environ``) to | |
| #: use, and ``default_value`` is the value to use if no value is otherwise | |
| #: found. | |
| #: NOTE: Fixing the spelling of this variable would be a breaking change. | |
| #: Please leave as is. | |
| BOTOCORE_DEFAUT_SESSION_VARIABLES = { | |
| # logical: config_file, env_var, default_value, conversion_func | |
| 'profile': (None, ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE'], None, None), | |
| 'region': ('region', 'AWS_DEFAULT_REGION', None, None), | |
| 'data_path': ('data_path', 'AWS_DATA_PATH', None, None), | |
| 'config_file': (None, 'AWS_CONFIG_FILE', '~/.aws/config', None), | |
| 'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None), | |
| 'api_versions': ('api_versions', None, {}, None), | |
| # This is the shared credentials file amongst sdks. | |
| 'credentials_file': ( | |
| None, | |
| 'AWS_SHARED_CREDENTIALS_FILE', | |
| '~/.aws/credentials', | |
| None, | |
| ), | |
| # These variables only exist in the config file. | |
| # This is the number of seconds until we time out a request to | |
| # the instance metadata service. | |
| 'metadata_service_timeout': ( | |
| 'metadata_service_timeout', | |
| 'AWS_METADATA_SERVICE_TIMEOUT', | |
| 1, | |
| int, | |
| ), | |
| # This is the number of request attempts we make until we give | |
| # up trying to retrieve data from the instance metadata service. | |
| 'metadata_service_num_attempts': ( | |
| 'metadata_service_num_attempts', | |
| 'AWS_METADATA_SERVICE_NUM_ATTEMPTS', | |
| 1, | |
| int, | |
| ), | |
| 'ec2_metadata_service_endpoint': ( | |
| 'ec2_metadata_service_endpoint', | |
| 'AWS_EC2_METADATA_SERVICE_ENDPOINT', | |
| None, | |
| None, | |
| ), | |
| 'ec2_metadata_service_endpoint_mode': ( | |
| 'ec2_metadata_service_endpoint_mode', | |
| 'AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE', | |
| None, | |
| None, | |
| ), | |
| 'ec2_metadata_v1_disabled': ( | |
| 'ec2_metadata_v1_disabled', | |
| 'AWS_EC2_METADATA_V1_DISABLED', | |
| False, | |
| utils.ensure_boolean, | |
| ), | |
| 'imds_use_ipv6': ( | |
| 'imds_use_ipv6', | |
| 'AWS_IMDS_USE_IPV6', | |
| False, | |
| utils.ensure_boolean, | |
| ), | |
| 'use_dualstack_endpoint': ( | |
| 'use_dualstack_endpoint', | |
| 'AWS_USE_DUALSTACK_ENDPOINT', | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'use_fips_endpoint': ( | |
| 'use_fips_endpoint', | |
| 'AWS_USE_FIPS_ENDPOINT', | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'ignore_configured_endpoint_urls': ( | |
| 'ignore_configured_endpoint_urls', | |
| 'AWS_IGNORE_CONFIGURED_ENDPOINT_URLS', | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'parameter_validation': ('parameter_validation', None, True, None), | |
| # Client side monitoring configurations. | |
| # Note: These configurations are considered internal to botocore. | |
| # Do not use them until publicly documented. | |
| 'csm_enabled': ( | |
| 'csm_enabled', | |
| 'AWS_CSM_ENABLED', | |
| False, | |
| utils.ensure_boolean, | |
| ), | |
| 'csm_host': ('csm_host', 'AWS_CSM_HOST', '127.0.0.1', None), | |
| 'csm_port': ('csm_port', 'AWS_CSM_PORT', 31000, int), | |
| 'csm_client_id': ('csm_client_id', 'AWS_CSM_CLIENT_ID', '', None), | |
| # Endpoint discovery configuration | |
| 'endpoint_discovery_enabled': ( | |
| 'endpoint_discovery_enabled', | |
| 'AWS_ENDPOINT_DISCOVERY_ENABLED', | |
| 'auto', | |
| None, | |
| ), | |
| 'sts_regional_endpoints': ( | |
| 'sts_regional_endpoints', | |
| 'AWS_STS_REGIONAL_ENDPOINTS', | |
| 'legacy', | |
| None, | |
| ), | |
| 'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', 'legacy', None), | |
| 'defaults_mode': ('defaults_mode', 'AWS_DEFAULTS_MODE', 'legacy', None), | |
| # We can't have a default here for v1 because we need to defer to | |
| # whatever the defaults are in _retry.json. | |
| 'max_attempts': ('max_attempts', 'AWS_MAX_ATTEMPTS', None, int), | |
| 'user_agent_appid': ('sdk_ua_app_id', 'AWS_SDK_UA_APP_ID', None, None), | |
| 'request_min_compression_size_bytes': ( | |
| 'request_min_compression_size_bytes', | |
| 'AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES', | |
| 10240, | |
| None, | |
| ), | |
| 'disable_request_compression': ( | |
| 'disable_request_compression', | |
| 'AWS_DISABLE_REQUEST_COMPRESSION', | |
| False, | |
| utils.ensure_boolean, | |
| ), | |
| 'sigv4a_signing_region_set': ( | |
| 'sigv4a_signing_region_set', | |
| 'AWS_SIGV4A_SIGNING_REGION_SET', | |
| None, | |
| None, | |
| ), | |
| } | |
| # A mapping for the s3 specific configuration vars. These are the configuration | |
| # vars that typically go in the s3 section of the config file. This mapping | |
| # follows the same schema as the previous session variable mapping. | |
| DEFAULT_S3_CONFIG_VARS = { | |
| 'addressing_style': (('s3', 'addressing_style'), None, None, None), | |
| 'use_accelerate_endpoint': ( | |
| ('s3', 'use_accelerate_endpoint'), | |
| None, | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'use_dualstack_endpoint': ( | |
| ('s3', 'use_dualstack_endpoint'), | |
| None, | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'payload_signing_enabled': ( | |
| ('s3', 'payload_signing_enabled'), | |
| None, | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'use_arn_region': ( | |
| ['s3_use_arn_region', ('s3', 'use_arn_region')], | |
| 'AWS_S3_USE_ARN_REGION', | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| 'us_east_1_regional_endpoint': ( | |
| [ | |
| 's3_us_east_1_regional_endpoint', | |
| ('s3', 'us_east_1_regional_endpoint'), | |
| ], | |
| 'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT', | |
| None, | |
| None, | |
| ), | |
| 's3_disable_multiregion_access_points': ( | |
| ('s3', 's3_disable_multiregion_access_points'), | |
| 'AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS', | |
| None, | |
| utils.ensure_boolean, | |
| ), | |
| } | |
| # A mapping for the proxy specific configuration vars. These are | |
| # used to configure how botocore interacts with proxy setups while | |
| # sending requests. | |
| DEFAULT_PROXIES_CONFIG_VARS = { | |
| 'proxy_ca_bundle': ('proxy_ca_bundle', None, None, None), | |
| 'proxy_client_cert': ('proxy_client_cert', None, None, None), | |
| 'proxy_use_forwarding_for_https': ( | |
| 'proxy_use_forwarding_for_https', | |
| None, | |
| None, | |
| utils.normalize_boolean, | |
| ), | |
| } | |
| def create_botocore_default_config_mapping(session): | |
| chain_builder = ConfigChainFactory(session=session) | |
| config_mapping = _create_config_chain_mapping( | |
| chain_builder, BOTOCORE_DEFAUT_SESSION_VARIABLES | |
| ) | |
| config_mapping['s3'] = SectionConfigProvider( | |
| 's3', | |
| session, | |
| _create_config_chain_mapping(chain_builder, DEFAULT_S3_CONFIG_VARS), | |
| ) | |
| config_mapping['proxies_config'] = SectionConfigProvider( | |
| 'proxies_config', | |
| session, | |
| _create_config_chain_mapping( | |
| chain_builder, DEFAULT_PROXIES_CONFIG_VARS | |
| ), | |
| ) | |
| return config_mapping | |
| def _create_config_chain_mapping(chain_builder, config_variables): | |
| mapping = {} | |
| for logical_name, config in config_variables.items(): | |
| mapping[logical_name] = chain_builder.create_config_chain( | |
| instance_name=logical_name, | |
| env_var_names=config[1], | |
| config_property_names=config[0], | |
| default=config[2], | |
| conversion_func=config[3], | |
| ) | |
| return mapping | |
| class DefaultConfigResolver: | |
| def __init__(self, default_config_data): | |
| self._base_default_config = default_config_data['base'] | |
| self._modes = default_config_data['modes'] | |
| self._resolved_default_configurations = {} | |
| def _resolve_default_values_by_mode(self, mode): | |
| default_config = self._base_default_config.copy() | |
| modifications = self._modes.get(mode) | |
| for config_var in modifications: | |
| default_value = default_config[config_var] | |
| modification_dict = modifications[config_var] | |
| modification = list(modification_dict.keys())[0] | |
| modification_value = modification_dict[modification] | |
| if modification == 'multiply': | |
| default_value *= modification_value | |
| elif modification == 'add': | |
| default_value += modification_value | |
| elif modification == 'override': | |
| default_value = modification_value | |
| default_config[config_var] = default_value | |
| return default_config | |
| def get_default_modes(self): | |
| default_modes = ['legacy', 'auto'] | |
| default_modes.extend(self._modes.keys()) | |
| return default_modes | |
| def get_default_config_values(self, mode): | |
| if mode not in self._resolved_default_configurations: | |
| defaults = self._resolve_default_values_by_mode(mode) | |
| self._resolved_default_configurations[mode] = defaults | |
| return self._resolved_default_configurations[mode] | |
| class ConfigChainFactory: | |
| """Factory class to create our most common configuration chain case. | |
| This is a convenience class to construct configuration chains that follow | |
| our most common pattern. This is to prevent ordering them incorrectly, | |
| and to make the config chain construction more readable. | |
| """ | |
| def __init__(self, session, environ=None): | |
| """Initialize a ConfigChainFactory. | |
| :type session: :class:`botocore.session.Session` | |
| :param session: This is the session that should be used to look up | |
| values from the config file. | |
| :type environ: dict | |
| :param environ: A mapping to use for environment variables. If this | |
| is not provided it will default to use os.environ. | |
| """ | |
| self._session = session | |
| if environ is None: | |
| environ = os.environ | |
| self._environ = environ | |
| def create_config_chain( | |
| self, | |
| instance_name=None, | |
| env_var_names=None, | |
| config_property_names=None, | |
| default=None, | |
| conversion_func=None, | |
| ): | |
| """Build a config chain following the standard botocore pattern. | |
| In botocore most of our config chains follow the the precendence: | |
| session_instance_variables, environment, config_file, default_value. | |
| This is a convenience function for creating a chain that follow | |
| that precendence. | |
| :type instance_name: str | |
| :param instance_name: This indicates what session instance variable | |
| corresponds to this config value. If it is None it will not be | |
| added to the chain. | |
| :type env_var_names: str or list of str or None | |
| :param env_var_names: One or more environment variable names to | |
| search for this value. They are searched in order. If it is None | |
| it will not be added to the chain. | |
| :type config_property_names: str/tuple or list of str/tuple or None | |
| :param config_property_names: One of more strings or tuples | |
| representing the name of the key in the config file for this | |
| config option. They are searched in order. If it is None it will | |
| not be added to the chain. | |
| :type default: Any | |
| :param default: Any constant value to be returned. | |
| :type conversion_func: None or callable | |
| :param conversion_func: If this value is None then it has no effect on | |
| the return type. Otherwise, it is treated as a function that will | |
| conversion_func our provided type. | |
| :rvalue: ConfigChain | |
| :returns: A ConfigChain that resolves in the order env_var_names -> | |
| config_property_name -> default. Any values that were none are | |
| omitted form the chain. | |
| """ | |
| providers = [] | |
| if instance_name is not None: | |
| providers.append( | |
| InstanceVarProvider( | |
| instance_var=instance_name, session=self._session | |
| ) | |
| ) | |
| if env_var_names is not None: | |
| providers.extend(self._get_env_providers(env_var_names)) | |
| if config_property_names is not None: | |
| providers.extend( | |
| self._get_scoped_config_providers(config_property_names) | |
| ) | |
| if default is not None: | |
| providers.append(ConstantProvider(value=default)) | |
| return ChainProvider( | |
| providers=providers, | |
| conversion_func=conversion_func, | |
| ) | |
| def _get_env_providers(self, env_var_names): | |
| env_var_providers = [] | |
| if not isinstance(env_var_names, list): | |
| env_var_names = [env_var_names] | |
| for env_var_name in env_var_names: | |
| env_var_providers.append( | |
| EnvironmentProvider(name=env_var_name, env=self._environ) | |
| ) | |
| return env_var_providers | |
| def _get_scoped_config_providers(self, config_property_names): | |
| scoped_config_providers = [] | |
| if not isinstance(config_property_names, list): | |
| config_property_names = [config_property_names] | |
| for config_property_name in config_property_names: | |
| scoped_config_providers.append( | |
| ScopedConfigProvider( | |
| config_var_name=config_property_name, | |
| session=self._session, | |
| ) | |
| ) | |
| return scoped_config_providers | |
| class ConfigValueStore: | |
| """The ConfigValueStore object stores configuration values.""" | |
| def __init__(self, mapping=None): | |
| """Initialize a ConfigValueStore. | |
| :type mapping: dict | |
| :param mapping: The mapping parameter is a map of string to a subclass | |
| of BaseProvider. When a config variable is asked for via the | |
| get_config_variable method, the corresponding provider will be | |
| invoked to load the value. | |
| """ | |
| self._overrides = {} | |
| self._mapping = {} | |
| if mapping is not None: | |
| for logical_name, provider in mapping.items(): | |
| self.set_config_provider(logical_name, provider) | |
| def __deepcopy__(self, memo): | |
| config_store = ConfigValueStore(copy.deepcopy(self._mapping, memo)) | |
| for logical_name, override_value in self._overrides.items(): | |
| config_store.set_config_variable(logical_name, override_value) | |
| return config_store | |
| def __copy__(self): | |
| config_store = ConfigValueStore(copy.copy(self._mapping)) | |
| for logical_name, override_value in self._overrides.items(): | |
| config_store.set_config_variable(logical_name, override_value) | |
| return config_store | |
| def get_config_variable(self, logical_name): | |
| """ | |
| Retrieve the value associeated with the specified logical_name | |
| from the corresponding provider. If no value is found None will | |
| be returned. | |
| :type logical_name: str | |
| :param logical_name: The logical name of the session variable | |
| you want to retrieve. This name will be mapped to the | |
| appropriate environment variable name for this session as | |
| well as the appropriate config file entry. | |
| :returns: value of variable or None if not defined. | |
| """ | |
| if logical_name in self._overrides: | |
| return self._overrides[logical_name] | |
| if logical_name not in self._mapping: | |
| return None | |
| provider = self._mapping[logical_name] | |
| return provider.provide() | |
| def get_config_provider(self, logical_name): | |
| """ | |
| Retrieve the provider associated with the specified logical_name. | |
| If no provider is found None will be returned. | |
| :type logical_name: str | |
| :param logical_name: The logical name of the session variable | |
| you want to retrieve. This name will be mapped to the | |
| appropriate environment variable name for this session as | |
| well as the appropriate config file entry. | |
| :returns: configuration provider or None if not defined. | |
| """ | |
| if ( | |
| logical_name in self._overrides | |
| or logical_name not in self._mapping | |
| ): | |
| return None | |
| provider = self._mapping[logical_name] | |
| return provider | |
| 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. | |
| """ | |
| self._overrides[logical_name] = value | |
| def clear_config_variable(self, logical_name): | |
| """Remove an override config variable from the session. | |
| :type logical_name: str | |
| :param logical_name: The name of the parameter to clear the override | |
| value from. | |
| """ | |
| self._overrides.pop(logical_name, None) | |
| def set_config_provider(self, logical_name, provider): | |
| """Set the provider for a config value. | |
| This provides control over how a particular configuration value is | |
| loaded. This replaces the provider for ``logical_name`` with the new | |
| ``provider``. | |
| :type logical_name: str | |
| :param logical_name: The name of the config value to change the config | |
| provider for. | |
| :type provider: :class:`botocore.configprovider.BaseProvider` | |
| :param provider: The new provider that should be responsible for | |
| providing a value for the config named ``logical_name``. | |
| """ | |
| self._mapping[logical_name] = provider | |
| class SmartDefaultsConfigStoreFactory: | |
| def __init__(self, default_config_resolver, imds_region_provider): | |
| self._default_config_resolver = default_config_resolver | |
| self._imds_region_provider = imds_region_provider | |
| # Initializing _instance_metadata_region as None so we | |
| # can fetch region in a lazy fashion only when needed. | |
| self._instance_metadata_region = None | |
| def merge_smart_defaults(self, config_store, mode, region_name): | |
| if mode == 'auto': | |
| mode = self.resolve_auto_mode(region_name) | |
| default_configs = ( | |
| self._default_config_resolver.get_default_config_values(mode) | |
| ) | |
| for config_var in default_configs: | |
| config_value = default_configs[config_var] | |
| method = getattr(self, f'_set_{config_var}', None) | |
| if method: | |
| method(config_store, config_value) | |
| def resolve_auto_mode(self, region_name): | |
| current_region = None | |
| if os.environ.get('AWS_EXECUTION_ENV'): | |
| default_region = os.environ.get('AWS_DEFAULT_REGION') | |
| current_region = os.environ.get('AWS_REGION', default_region) | |
| if not current_region: | |
| if self._instance_metadata_region: | |
| current_region = self._instance_metadata_region | |
| else: | |
| try: | |
| current_region = self._imds_region_provider.provide() | |
| self._instance_metadata_region = current_region | |
| except Exception: | |
| pass | |
| if current_region: | |
| if region_name == current_region: | |
| return 'in-region' | |
| else: | |
| return 'cross-region' | |
| return 'standard' | |
| def _update_provider(self, config_store, variable, value): | |
| original_provider = config_store.get_config_provider(variable) | |
| default_provider = ConstantProvider(value) | |
| if isinstance(original_provider, ChainProvider): | |
| chain_provider_copy = copy.deepcopy(original_provider) | |
| chain_provider_copy.set_default_provider(default_provider) | |
| default_provider = chain_provider_copy | |
| elif isinstance(original_provider, BaseProvider): | |
| default_provider = ChainProvider( | |
| providers=[original_provider, default_provider] | |
| ) | |
| config_store.set_config_provider(variable, default_provider) | |
| def _update_section_provider( | |
| self, config_store, section_name, variable, value | |
| ): | |
| section_provider_copy = copy.deepcopy( | |
| config_store.get_config_provider(section_name) | |
| ) | |
| section_provider_copy.set_default_provider( | |
| variable, ConstantProvider(value) | |
| ) | |
| config_store.set_config_provider(section_name, section_provider_copy) | |
| def _set_retryMode(self, config_store, value): | |
| self._update_provider(config_store, 'retry_mode', value) | |
| def _set_stsRegionalEndpoints(self, config_store, value): | |
| self._update_provider(config_store, 'sts_regional_endpoints', value) | |
| def _set_s3UsEast1RegionalEndpoints(self, config_store, value): | |
| self._update_section_provider( | |
| config_store, 's3', 'us_east_1_regional_endpoint', value | |
| ) | |
| def _set_connectTimeoutInMillis(self, config_store, value): | |
| self._update_provider(config_store, 'connect_timeout', value / 1000) | |
| class BaseProvider: | |
| """Base class for configuration value providers. | |
| A configuration provider has some method of providing a configuration | |
| value. | |
| """ | |
| def provide(self): | |
| """Provide a config value.""" | |
| raise NotImplementedError('provide') | |
| class ChainProvider(BaseProvider): | |
| """This provider wraps one or more other providers. | |
| Each provider in the chain is called, the first one returning a non-None | |
| value is then returned. | |
| """ | |
| def __init__(self, providers=None, conversion_func=None): | |
| """Initalize a ChainProvider. | |
| :type providers: list | |
| :param providers: The initial list of providers to check for values | |
| when invoked. | |
| :type conversion_func: None or callable | |
| :param conversion_func: If this value is None then it has no affect on | |
| the return type. Otherwise, it is treated as a function that will | |
| transform provided value. | |
| """ | |
| if providers is None: | |
| providers = [] | |
| self._providers = providers | |
| self._conversion_func = conversion_func | |
| def __deepcopy__(self, memo): | |
| return ChainProvider( | |
| copy.deepcopy(self._providers, memo), self._conversion_func | |
| ) | |
| def provide(self): | |
| """Provide the value from the first provider to return non-None. | |
| Each provider in the chain has its provide method called. The first | |
| one in the chain to return a non-None value is the returned from the | |
| ChainProvider. When no non-None value is found, None is returned. | |
| """ | |
| for provider in self._providers: | |
| value = provider.provide() | |
| if value is not None: | |
| return self._convert_type(value) | |
| return None | |
| def set_default_provider(self, default_provider): | |
| if self._providers and isinstance( | |
| self._providers[-1], ConstantProvider | |
| ): | |
| self._providers[-1] = default_provider | |
| else: | |
| self._providers.append(default_provider) | |
| num_of_constants = sum( | |
| isinstance(provider, ConstantProvider) | |
| for provider in self._providers | |
| ) | |
| if num_of_constants > 1: | |
| logger.info( | |
| 'ChainProvider object contains multiple ' | |
| 'instances of ConstantProvider objects' | |
| ) | |
| def _convert_type(self, value): | |
| if self._conversion_func is not None: | |
| return self._conversion_func(value) | |
| return value | |
| def __repr__(self): | |
| return '[{}]'.format(', '.join([str(p) for p in self._providers])) | |
| class InstanceVarProvider(BaseProvider): | |
| """This class loads config values from the session instance vars.""" | |
| def __init__(self, instance_var, session): | |
| """Initialize InstanceVarProvider. | |
| :type instance_var: str | |
| :param instance_var: The instance variable to load from the session. | |
| :type session: :class:`botocore.session.Session` | |
| :param session: The botocore session to get the loaded configuration | |
| file variables from. | |
| """ | |
| self._instance_var = instance_var | |
| self._session = session | |
| def __deepcopy__(self, memo): | |
| return InstanceVarProvider( | |
| copy.deepcopy(self._instance_var, memo), self._session | |
| ) | |
| def provide(self): | |
| """Provide a config value from the session instance vars.""" | |
| instance_vars = self._session.instance_variables() | |
| value = instance_vars.get(self._instance_var) | |
| return value | |
| def __repr__(self): | |
| return f'InstanceVarProvider(instance_var={self._instance_var}, session={self._session})' | |
| class ScopedConfigProvider(BaseProvider): | |
| def __init__(self, config_var_name, session): | |
| """Initialize ScopedConfigProvider. | |
| :type config_var_name: str or tuple | |
| :param config_var_name: The name of the config variable to load from | |
| the configuration file. If the value is a tuple, it must only | |
| consist of two items, where the first item represents the section | |
| and the second item represents the config var name in the section. | |
| :type session: :class:`botocore.session.Session` | |
| :param session: The botocore session to get the loaded configuration | |
| file variables from. | |
| """ | |
| self._config_var_name = config_var_name | |
| self._session = session | |
| def __deepcopy__(self, memo): | |
| return ScopedConfigProvider( | |
| copy.deepcopy(self._config_var_name, memo), self._session | |
| ) | |
| def provide(self): | |
| """Provide a value from a config file property.""" | |
| scoped_config = self._session.get_scoped_config() | |
| if isinstance(self._config_var_name, tuple): | |
| section_config = scoped_config.get(self._config_var_name[0]) | |
| if not isinstance(section_config, dict): | |
| return None | |
| return section_config.get(self._config_var_name[1]) | |
| return scoped_config.get(self._config_var_name) | |
| def __repr__(self): | |
| return f'ScopedConfigProvider(config_var_name={self._config_var_name}, session={self._session})' | |
| class EnvironmentProvider(BaseProvider): | |
| """This class loads config values from environment variables.""" | |
| def __init__(self, name, env): | |
| """Initialize with the keys in the dictionary to check. | |
| :type name: str | |
| :param name: The key with that name will be loaded and returned. | |
| :type env: dict | |
| :param env: Environment variables dictionary to get variables from. | |
| """ | |
| self._name = name | |
| self._env = env | |
| def __deepcopy__(self, memo): | |
| return EnvironmentProvider( | |
| copy.deepcopy(self._name, memo), copy.deepcopy(self._env, memo) | |
| ) | |
| def provide(self): | |
| """Provide a config value from a source dictionary.""" | |
| if self._name in self._env: | |
| return self._env[self._name] | |
| return None | |
| def __repr__(self): | |
| return f'EnvironmentProvider(name={self._name}, env={self._env})' | |
| class SectionConfigProvider(BaseProvider): | |
| """Provides a dictionary from a section in the scoped config | |
| This is useful for retrieving scoped config variables (i.e. s3) that have | |
| their own set of config variables and resolving logic. | |
| """ | |
| def __init__(self, section_name, session, override_providers=None): | |
| self._section_name = section_name | |
| self._session = session | |
| self._scoped_config_provider = ScopedConfigProvider( | |
| self._section_name, self._session | |
| ) | |
| self._override_providers = override_providers | |
| if self._override_providers is None: | |
| self._override_providers = {} | |
| def __deepcopy__(self, memo): | |
| return SectionConfigProvider( | |
| copy.deepcopy(self._section_name, memo), | |
| self._session, | |
| copy.deepcopy(self._override_providers, memo), | |
| ) | |
| def provide(self): | |
| section_config = self._scoped_config_provider.provide() | |
| if section_config and not isinstance(section_config, dict): | |
| logger.debug( | |
| "The %s config key is not a dictionary type, " | |
| "ignoring its value of: %s", | |
| self._section_name, | |
| section_config, | |
| ) | |
| return None | |
| for section_config_var, provider in self._override_providers.items(): | |
| provider_val = provider.provide() | |
| if provider_val is not None: | |
| if section_config is None: | |
| section_config = {} | |
| section_config[section_config_var] = provider_val | |
| return section_config | |
| def set_default_provider(self, key, default_provider): | |
| provider = self._override_providers.get(key) | |
| if isinstance(provider, ChainProvider): | |
| provider.set_default_provider(default_provider) | |
| return | |
| elif isinstance(provider, BaseProvider): | |
| default_provider = ChainProvider( | |
| providers=[provider, default_provider] | |
| ) | |
| self._override_providers[key] = default_provider | |
| def __repr__(self): | |
| return ( | |
| f'SectionConfigProvider(section_name={self._section_name}, ' | |
| f'session={self._session}, ' | |
| f'override_providers={self._override_providers})' | |
| ) | |
| class ConstantProvider(BaseProvider): | |
| """This provider provides a constant value.""" | |
| def __init__(self, value): | |
| self._value = value | |
| def __deepcopy__(self, memo): | |
| return ConstantProvider(copy.deepcopy(self._value, memo)) | |
| def provide(self): | |
| """Provide the constant value given during initialization.""" | |
| return self._value | |
| def __repr__(self): | |
| return f'ConstantProvider(value={self._value})' | |
| class ConfiguredEndpointProvider(BaseProvider): | |
| """Lookup an endpoint URL from environment variable or shared config file. | |
| NOTE: This class is considered private and is subject to abrupt breaking | |
| changes or removal without prior announcement. Please do not use it | |
| directly. | |
| """ | |
| _ENDPOINT_URL_LOOKUP_ORDER = [ | |
| 'environment_service', | |
| 'environment_global', | |
| 'config_service', | |
| 'config_global', | |
| ] | |
| def __init__( | |
| self, | |
| full_config, | |
| scoped_config, | |
| client_name, | |
| environ=None, | |
| ): | |
| """Initialize a ConfiguredEndpointProviderChain. | |
| :type full_config: dict | |
| :param full_config: This is the dict representing the full | |
| configuration file. | |
| :type scoped_config: dict | |
| :param scoped_config: This is the dict representing the configuration | |
| for the current profile for the session. | |
| :type client_name: str | |
| :param client_name: The name used to instantiate a client using | |
| botocore.session.Session.create_client. | |
| :type environ: dict | |
| :param environ: A mapping to use for environment variables. If this | |
| is not provided it will default to use os.environ. | |
| """ | |
| self._full_config = full_config | |
| self._scoped_config = scoped_config | |
| self._client_name = client_name | |
| self._transformed_service_id = self._get_snake_case_service_id( | |
| self._client_name | |
| ) | |
| if environ is None: | |
| environ = os.environ | |
| self._environ = environ | |
| def provide(self): | |
| """Lookup the configured endpoint URL. | |
| The order is: | |
| 1. The value provided by a service-specific environment variable. | |
| 2. The value provided by the global endpoint environment variable | |
| (AWS_ENDPOINT_URL). | |
| 3. The value provided by a service-specific parameter from a services | |
| definition section in the shared configuration file. | |
| 4. The value provided by the global parameter from a services | |
| definition section in the shared configuration file. | |
| """ | |
| for location in self._ENDPOINT_URL_LOOKUP_ORDER: | |
| logger.debug( | |
| 'Looking for endpoint for %s via: %s', | |
| self._client_name, | |
| location, | |
| ) | |
| endpoint_url = getattr(self, f'_get_endpoint_url_{location}')() | |
| if endpoint_url: | |
| logger.info( | |
| 'Found endpoint for %s via: %s.', | |
| self._client_name, | |
| location, | |
| ) | |
| return endpoint_url | |
| logger.debug('No configured endpoint found.') | |
| return None | |
| def _get_snake_case_service_id(self, client_name): | |
| # Get the service ID without loading the service data file, accounting | |
| # for any aliases and standardizing the names with hyphens. | |
| client_name = utils.SERVICE_NAME_ALIASES.get(client_name, client_name) | |
| hyphenized_service_id = ( | |
| utils.CLIENT_NAME_TO_HYPHENIZED_SERVICE_ID_OVERRIDES.get( | |
| client_name, client_name | |
| ) | |
| ) | |
| return hyphenized_service_id.replace('-', '_') | |
| def _get_service_env_var_name(self): | |
| transformed_service_id_env = self._transformed_service_id.upper() | |
| return f'AWS_ENDPOINT_URL_{transformed_service_id_env}' | |
| def _get_services_config(self): | |
| if 'services' not in self._scoped_config: | |
| return {} | |
| section_name = self._scoped_config['services'] | |
| services_section = self._full_config.get('services', {}).get( | |
| section_name | |
| ) | |
| if not services_section: | |
| error_msg = ( | |
| f'The profile is configured to use the services ' | |
| f'section but the "{section_name}" services ' | |
| f'configuration does not exist.' | |
| ) | |
| raise InvalidConfigError(error_msg=error_msg) | |
| return services_section | |
| def _get_endpoint_url_config_service(self): | |
| snakecase_service_id = self._transformed_service_id.lower() | |
| return ( | |
| self._get_services_config() | |
| .get(snakecase_service_id, {}) | |
| .get('endpoint_url') | |
| ) | |
| def _get_endpoint_url_config_global(self): | |
| return self._scoped_config.get('endpoint_url') | |
| def _get_endpoint_url_environment_service(self): | |
| return EnvironmentProvider( | |
| name=self._get_service_env_var_name(), env=self._environ | |
| ).provide() | |
| def _get_endpoint_url_environment_global(self): | |
| return EnvironmentProvider( | |
| name='AWS_ENDPOINT_URL', env=self._environ | |
| ).provide() | |