| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """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__) |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| BOTOCORE_DEFAUT_SESSION_VARIABLES = { |
| |
| '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), |
| |
| 'credentials_file': ( |
| None, |
| 'AWS_SHARED_CREDENTIALS_FILE', |
| '~/.aws/credentials', |
| None, |
| ), |
| |
| |
| |
| 'metadata_service_timeout': ( |
| 'metadata_service_timeout', |
| 'AWS_METADATA_SERVICE_TIMEOUT', |
| 1, |
| int, |
| ), |
| |
| |
| '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), |
| |
| |
| |
| '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_enabled': ( |
| 'endpoint_discovery_enabled', |
| 'AWS_ENDPOINT_DISCOVERY_ENABLED', |
| 'auto', |
| None, |
| ), |
| 'sts_regional_endpoints': ( |
| 'sts_regional_endpoints', |
| 'AWS_STS_REGIONAL_ENDPOINTS', |
| 'regional', |
| None, |
| ), |
| 'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', 'legacy', None), |
| 'defaults_mode': ('defaults_mode', 'AWS_DEFAULTS_MODE', 'legacy', None), |
| |
| |
| '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, |
| ), |
| 'request_checksum_calculation': ( |
| 'request_checksum_calculation', |
| 'AWS_REQUEST_CHECKSUM_CALCULATION', |
| "when_supported", |
| None, |
| ), |
| 'response_checksum_validation': ( |
| 'response_checksum_validation', |
| 'AWS_RESPONSE_CHECKSUM_VALIDATION', |
| "when_supported", |
| None, |
| ), |
| 'account_id_endpoint_mode': ( |
| 'account_id_endpoint_mode', |
| 'AWS_ACCOUNT_ID_ENDPOINT_MODE', |
| 'preferred', |
| None, |
| ), |
| 'disable_host_prefix_injection': ( |
| 'disable_host_prefix_injection', |
| 'AWS_DISABLE_HOST_PREFIX_INJECTION', |
| None, |
| utils.ensure_boolean, |
| ), |
| 'auth_scheme_preference': ( |
| 'auth_scheme_preference', |
| 'AWS_AUTH_SCHEME_PREFERENCE', |
| None, |
| None, |
| ), |
| } |
| |
| |
| |
| 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, |
| ), |
| } |
| |
| |
| |
| 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 associated 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 |
| |
| |
| 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): |
| |
| |
| 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() |
|
|