1
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13 11
"""This module contains the inteface for controlling how configuration
14
is loaded.
15
"""
16 11
import logging
17 11
import os
18

19 11
from botocore import utils
20

21

22 11
logger = logging.getLogger(__name__)
23

24

25
#: A default dictionary that maps the logical names for session variables
26
#: to the specific environment variables and configuration file names
27
#: that contain the values for these variables.
28
#: When creating a new Session object, you can pass in your own dictionary
29
#: to remap the logical names or to add new logical names.  You can then
30
#: get the current value for these variables by using the
31
#: ``get_config_variable`` method of the :class:`botocore.session.Session`
32
#: class.
33
#: These form the keys of the dictionary.  The values in the dictionary
34
#: are tuples of (<config_name>, <environment variable>, <default value>,
35
#: <conversion func>).
36
#: The conversion func is a function that takes the configuration value
37
#: as an argument and returns the converted value.  If this value is
38
#: None, then the configuration value is returned unmodified.  This
39
#: conversion function can be used to type convert config values to
40
#: values other than the default values of strings.
41
#: The ``profile`` and ``config_file`` variables should always have a
42
#: None value for the first entry in the tuple because it doesn't make
43
#: sense to look inside the config file for the location of the config
44
#: file or for the default profile to use.
45
#: The ``config_name`` is the name to look for in the configuration file,
46
#: the ``env var`` is the OS environment variable (``os.environ``) to
47
#: use, and ``default_value`` is the value to use if no value is otherwise
48
#: found.
49 11
BOTOCORE_DEFAUT_SESSION_VARIABLES = {
50
    # logical:  config_file, env_var,        default_value, conversion_func
51
    'profile': (None, ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE'], None, None),
52
    'region': ('region', 'AWS_DEFAULT_REGION', None, None),
53
    'data_path': ('data_path', 'AWS_DATA_PATH', None, None),
54
    'config_file': (None, 'AWS_CONFIG_FILE', '~/.aws/config', None),
55
    'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None),
56
    'api_versions': ('api_versions', None, {}, None),
57

58
    # This is the shared credentials file amongst sdks.
59
    'credentials_file': (None, 'AWS_SHARED_CREDENTIALS_FILE',
60
                         '~/.aws/credentials', None),
61

62
    # These variables only exist in the config file.
63

64
    # This is the number of seconds until we time out a request to
65
    # the instance metadata service.
66
    'metadata_service_timeout': (
67
        'metadata_service_timeout',
68
        'AWS_METADATA_SERVICE_TIMEOUT', 1, int),
69
    # This is the number of request attempts we make until we give
70
    # up trying to retrieve data from the instance metadata service.
71
    'metadata_service_num_attempts': (
72
        'metadata_service_num_attempts',
73
        'AWS_METADATA_SERVICE_NUM_ATTEMPTS', 1, int),
74
    'ec2_metadata_service_endpoint': (
75
        'ec2_metadata_service_endpoint',
76
        'AWS_EC2_METADATA_SERVICE_ENDPOINT',
77
        None, None),
78
    'imds_use_ipv6': (
79
        'imds_use_ipv6',
80
        'AWS_IMDS_USE_IPV6',
81
        False, None),
82
    'parameter_validation': ('parameter_validation', None, True, None),
83
    # Client side monitoring configurations.
84
    # Note: These configurations are considered internal to botocore.
85
    # Do not use them until publicly documented.
86
    'csm_enabled': (
87
            'csm_enabled', 'AWS_CSM_ENABLED', False, utils.ensure_boolean),
88
    'csm_host': ('csm_host', 'AWS_CSM_HOST', '127.0.0.1', None),
89
    'csm_port': ('csm_port', 'AWS_CSM_PORT', 31000, int),
90
    'csm_client_id': ('csm_client_id', 'AWS_CSM_CLIENT_ID', '', None),
91
    # Endpoint discovery configuration
92
    'endpoint_discovery_enabled': (
93
        'endpoint_discovery_enabled', 'AWS_ENDPOINT_DISCOVERY_ENABLED',
94
        'auto', None),
95
    'sts_regional_endpoints': (
96
        'sts_regional_endpoints', 'AWS_STS_REGIONAL_ENDPOINTS', 'legacy',
97
        None
98
    ),
99
    'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', 'legacy', None),
100
    # We can't have a default here for v1 because we need to defer to
101
    # whatever the defaults are in _retry.json.
102
    'max_attempts': ('max_attempts', 'AWS_MAX_ATTEMPTS', None, int),
103
}
104
# A mapping for the s3 specific configuration vars. These are the configuration
105
# vars that typically go in the s3 section of the config file. This mapping
106
# follows the same schema as the previous session variable mapping.
107 11
DEFAULT_S3_CONFIG_VARS = {
108
    'addressing_style': (
109
        ('s3', 'addressing_style'), None, None, None),
110
    'use_accelerate_endpoint': (
111
        ('s3', 'use_accelerate_endpoint'), None, None, utils.ensure_boolean
112
    ),
113
    'use_dualstack_endpoint': (
114
        ('s3', 'use_dualstack_endpoint'), None, None, utils.ensure_boolean
115
    ),
116
    'payload_signing_enabled': (
117
        ('s3', 'payload_signing_enabled'), None, None, utils.ensure_boolean
118
    ),
119
    'use_arn_region': (
120
        ['s3_use_arn_region',
121
         ('s3', 'use_arn_region')],
122
        'AWS_S3_USE_ARN_REGION', None, utils.ensure_boolean
123
    ),
124
    'us_east_1_regional_endpoint': (
125
        ['s3_us_east_1_regional_endpoint',
126
         ('s3', 'us_east_1_regional_endpoint')],
127
        'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT', None, None
128
    )
129
}
130

131

132 11
def create_botocore_default_config_mapping(session):
133 11
    chain_builder = ConfigChainFactory(session=session)
134 11
    config_mapping = _create_config_chain_mapping(
135
        chain_builder, BOTOCORE_DEFAUT_SESSION_VARIABLES)
136 11
    config_mapping['s3'] = SectionConfigProvider(
137
        's3', session, _create_config_chain_mapping(
138
            chain_builder, DEFAULT_S3_CONFIG_VARS)
139
    )
140 11
    return config_mapping
141

142

143 11
def _create_config_chain_mapping(chain_builder, config_variables):
144 11
    mapping = {}
145 11
    for logical_name, config in config_variables.items():
146 11
        mapping[logical_name] = chain_builder.create_config_chain(
147
            instance_name=logical_name,
148
            env_var_names=config[1],
149
            config_property_names=config[0],
150
            default=config[2],
151
            conversion_func=config[3]
152
        )
153 11
    return mapping
154

155

156 11
class ConfigChainFactory(object):
157
    """Factory class to create our most common configuration chain case.
158

159
    This is a convenience class to construct configuration chains that follow
160
    our most common pattern. This is to prevent ordering them incorrectly,
161
    and to make the config chain construction more readable.
162
    """
163 11
    def __init__(self, session, environ=None):
164
        """Initialize a ConfigChainFactory.
165

166
        :type session: :class:`botocore.session.Session`
167
        :param session: This is the session that should be used to look up
168
            values from the config file.
169

170
        :type environ: dict
171
        :param environ: A mapping to use for environment variables. If this
172
            is not provided it will default to use os.environ.
173
        """
174 11
        self._session = session
175 11
        if environ is None:
176 11
            environ = os.environ
177 11
        self._environ = environ
178

179 11
    def create_config_chain(self, instance_name=None, env_var_names=None,
180
                            config_property_names=None, default=None,
181
                            conversion_func=None):
182
        """Build a config chain following the standard botocore pattern.
183

184
        In botocore most of our config chains follow the the precendence:
185
        session_instance_variables, environment, config_file, default_value.
186

187
        This is a convenience function for creating a chain that follow
188
        that precendence.
189

190
        :type instance_name: str
191
        :param instance_name: This indicates what session instance variable
192
            corresponds to this config value. If it is None it will not be
193
            added to the chain.
194

195
        :type env_var_names: str or list of str or None
196
        :param env_var_names: One or more environment variable names to
197
            search for this value. They are searched in order. If it is None
198
            it will not be added to the chain.
199

200
        :type config_property_names: str/tuple or list of str/tuple or None
201
        :param config_property_names: One of more strings or tuples
202
            representing the name of the key in the config file for this
203
            config option. They are searched in order. If it is None it will
204
            not be added to the chain.
205

206
        :type default: Any
207
        :param default: Any constant value to be returned.
208

209
        :type conversion_func: None or callable
210
        :param conversion_func: If this value is None then it has no effect on
211
            the return type. Otherwise, it is treated as a function that will
212
            conversion_func our provided type.
213

214
        :rvalue: ConfigChain
215
        :returns: A ConfigChain that resolves in the order env_var_names ->
216
            config_property_name -> default. Any values that were none are
217
            omitted form the chain.
218
        """
219 11
        providers = []
220 11
        if instance_name is not None:
221 11
            providers.append(
222
                InstanceVarProvider(
223
                    instance_var=instance_name,
224
                    session=self._session
225
                )
226
            )
227 11
        if env_var_names is not None:
228 11
            providers.extend(self._get_env_providers(env_var_names))
229 11
        if config_property_names is not None:
230 11
            providers.extend(
231
                self._get_scoped_config_providers(config_property_names)
232
            )
233 11
        if default is not None:
234 11
            providers.append(ConstantProvider(value=default))
235

236 11
        return ChainProvider(
237
            providers=providers,
238
            conversion_func=conversion_func,
239
        )
240

241 11
    def _get_env_providers(self, env_var_names):
242 11
        env_var_providers = []
243 11
        if not isinstance(env_var_names, list):
244 11
            env_var_names = [env_var_names]
245 11
        for env_var_name in env_var_names:
246 11
            env_var_providers.append(
247
                EnvironmentProvider(name=env_var_name, env=self._environ)
248
            )
249 11
        return env_var_providers
250

251 11
    def _get_scoped_config_providers(self, config_property_names):
252 11
        scoped_config_providers = []
253 11
        if not isinstance(config_property_names, list):
254 11
            config_property_names = [config_property_names]
255 11
        for config_property_name in config_property_names:
256 11
            scoped_config_providers.append(
257
                ScopedConfigProvider(
258
                    config_var_name=config_property_name,
259
                    session=self._session,
260
                )
261
            )
262 11
        return scoped_config_providers
263

264

265 11
class ConfigValueStore(object):
266
    """The ConfigValueStore object stores configuration values."""
267 11
    def __init__(self, mapping=None):
268
        """Initialize a ConfigValueStore.
269

270
        :type mapping: dict
271
        :param mapping: The mapping parameter is a map of string to a subclass
272
            of BaseProvider. When a config variable is asked for via the
273
            get_config_variable method, the corresponding provider will be
274
            invoked to load the value.
275
        """
276 11
        self._overrides = {}
277 11
        self._mapping = {}
278 11
        if mapping is not None:
279 11
            for logical_name, provider in mapping.items():
280 11
                self.set_config_provider(logical_name, provider)
281

282 11
    def get_config_variable(self, logical_name):
283
        """
284
        Retrieve the value associeated with the specified logical_name
285
        from the corresponding provider. If no value is found None will
286
        be returned.
287

288
        :type logical_name: str
289
        :param logical_name: The logical name of the session variable
290
            you want to retrieve.  This name will be mapped to the
291
            appropriate environment variable name for this session as
292
            well as the appropriate config file entry.
293

294
        :returns: value of variable or None if not defined.
295
        """
296 11
        if logical_name in self._overrides:
297 11
            return self._overrides[logical_name]
298 11
        if logical_name not in self._mapping:
299 11
            return None
300 11
        provider = self._mapping[logical_name]
301 11
        return provider.provide()
302

303 11
    def set_config_variable(self, logical_name, value):
304
        """Set a configuration variable to a specific value.
305

306
        By using this method, you can override the normal lookup
307
        process used in ``get_config_variable`` by explicitly setting
308
        a value.  Subsequent calls to ``get_config_variable`` will
309
        use the ``value``.  This gives you per-session specific
310
        configuration values.
311

312
        ::
313
            >>> # Assume logical name 'foo' maps to env var 'FOO'
314
            >>> os.environ['FOO'] = 'myvalue'
315
            >>> s.get_config_variable('foo')
316
            'myvalue'
317
            >>> s.set_config_variable('foo', 'othervalue')
318
            >>> s.get_config_variable('foo')
319
            'othervalue'
320

321
        :type logical_name: str
322
        :param logical_name: The logical name of the session variable
323
            you want to set.  These are the keys in ``SESSION_VARIABLES``.
324

325
        :param value: The value to associate with the config variable.
326
        """
327 11
        self._overrides[logical_name] = value
328

329 11
    def clear_config_variable(self, logical_name):
330
        """Remove an override config variable from the session.
331

332
        :type logical_name: str
333
        :param logical_name: The name of the parameter to clear the override
334
            value from.
335
        """
336 0
        self._overrides.pop(logical_name, None)
337

338 11
    def set_config_provider(self, logical_name, provider):
339
        """Set the provider for a config value.
340

341
        This provides control over how a particular configuration value is
342
        loaded. This replaces the provider for ``logical_name`` with the new
343
        ``provider``.
344

345
        :type logical_name: str
346
        :param logical_name: The name of the config value to change the config
347
            provider for.
348

349
        :type provider: :class:`botocore.configprovider.BaseProvider`
350
        :param provider: The new provider that should be responsible for
351
            providing a value for the config named ``logical_name``.
352
        """
353 11
        self._mapping[logical_name] = provider
354

355

356 11
class BaseProvider(object):
357
    """Base class for configuration value providers.
358

359
    A configuration provider has some method of providing a configuration
360
    value.
361
    """
362 11
    def provide(self):
363
        """Provide a config value."""
364
        raise NotImplementedError('provide')
365

366

367 11
class ChainProvider(BaseProvider):
368
    """This provider wraps one or more other providers.
369

370
    Each provider in the chain is called, the first one returning a non-None
371
    value is then returned.
372
    """
373 11
    def __init__(self, providers=None, conversion_func=None):
374
        """Initalize a ChainProvider.
375

376
        :type providers: list
377
        :param providers: The initial list of providers to check for values
378
            when invoked.
379

380
        :type conversion_func: None or callable
381
        :param conversion_func: If this value is None then it has no affect on
382
            the return type. Otherwise, it is treated as a function that will
383
            transform provided value.
384
        """
385 11
        if providers is None:
386 0
            providers = []
387 11
        self._providers = providers
388 11
        self._conversion_func = conversion_func
389

390 11
    def provide(self):
391
        """Provide the value from the first provider to return non-None.
392

393
        Each provider in the chain has its provide method called. The first
394
        one in the chain to return a non-None value is the returned from the
395
        ChainProvider. When no non-None value is found, None is returned.
396
        """
397 11
        for provider in self._providers:
398 11
            value = provider.provide()
399 11
            if value is not None:
400 11
                return self._convert_type(value)
401 11
        return None
402

403 11
    def _convert_type(self, value):
404 11
        if self._conversion_func is not None:
405 11
            return self._conversion_func(value)
406 11
        return value
407

408 11
    def __repr__(self):
409 0
        return '[%s]' % ', '.join([str(p) for p in self._providers])
410

411

412 11
class InstanceVarProvider(BaseProvider):
413
    """This class loads config values from the session instance vars."""
414 11
    def __init__(self, instance_var, session):
415
        """Initialize InstanceVarProvider.
416

417
        :type instance_var: str
418
        :param instance_var: The instance variable to load from the session.
419

420
        :type session: :class:`botocore.session.Session`
421
        :param session: The botocore session to get the loaded configuration
422
            file variables from.
423
        """
424 11
        self._instance_var = instance_var
425 11
        self._session = session
426

427 11
    def provide(self):
428
        """Provide a config value from the session instance vars."""
429 11
        instance_vars = self._session.instance_variables()
430 11
        value = instance_vars.get(self._instance_var)
431 11
        return value
432

433 11
    def __repr__(self):
434 0
        return 'InstanceVarProvider(instance_var=%s, session=%s)' % (
435
            self._instance_var,
436
            self._session,
437
        )
438

439

440 11
class ScopedConfigProvider(BaseProvider):
441 11
    def __init__(self, config_var_name, session):
442
        """Initialize ScopedConfigProvider.
443

444
        :type config_var_name: str or tuple
445
        :param config_var_name: The name of the config variable to load from
446
            the configuration file. If the value is a tuple, it must only
447
            consist of two items, where the first item represents the section
448
            and the second item represents the config var name in the section.
449

450
        :type session: :class:`botocore.session.Session`
451
        :param session: The botocore session to get the loaded configuration
452
            file variables from.
453
        """
454 11
        self._config_var_name = config_var_name
455 11
        self._session = session
456

457 11
    def provide(self):
458
        """Provide a value from a config file property."""
459 11
        scoped_config = self._session.get_scoped_config()
460 11
        if isinstance(self._config_var_name, tuple):
461 11
            section_config = scoped_config.get(self._config_var_name[0])
462 11
            if not isinstance(section_config, dict):
463 11
                return None
464 11
            return section_config.get(self._config_var_name[1])
465 11
        return scoped_config.get(self._config_var_name)
466

467 11
    def __repr__(self):
468 0
        return 'ScopedConfigProvider(config_var_name=%s, session=%s)' % (
469
            self._config_var_name,
470
            self._session,
471
        )
472

473

474 11
class EnvironmentProvider(BaseProvider):
475
    """This class loads config values from environment variables."""
476 11
    def __init__(self, name, env):
477
        """Initialize with the keys in the dictionary to check.
478

479
        :type name: str
480
        :param name: The key with that name will be loaded and returned.
481

482
        :type env: dict
483
        :param env: Environment variables dictionary to get variables from.
484
        """
485 11
        self._name = name
486 11
        self._env = env
487

488 11
    def provide(self):
489
        """Provide a config value from a source dictionary."""
490 11
        if self._name in self._env:
491 11
            return self._env[self._name]
492 11
        return None
493

494 11
    def __repr__(self):
495 0
        return 'EnvironmentProvider(name=%s, env=%s)' % (self._name, self._env)
496

497

498 11
class SectionConfigProvider(BaseProvider):
499
    """Provides a dictionary from a section in the scoped config
500

501
    This is useful for retrieving scoped config variables (i.e. s3) that have
502
    their own set of config variables and resolving logic.
503
    """
504 11
    def __init__(self, section_name, session, override_providers=None):
505 11
        self._section_name = section_name
506 11
        self._session = session
507 11
        self._scoped_config_provider = ScopedConfigProvider(
508
            self._section_name, self._session)
509 11
        self._override_providers = override_providers
510 11
        if self._override_providers is None:
511 11
            self._override_providers = {}
512

513 11
    def provide(self):
514 11
        section_config = self._scoped_config_provider.provide()
515 11
        if section_config and not isinstance(section_config, dict):
516 0
            logger.debug("The %s config key is not a dictionary type, "
517
                         "ignoring its value of: %s", self._section_name,
518
                         section_config)
519 0
            return None
520 11
        for section_config_var, provider in self._override_providers.items():
521 11
            provider_val = provider.provide()
522 11
            if provider_val is not None:
523 11
                if section_config is None:
524 11
                    section_config = {}
525 11
                section_config[section_config_var] = provider_val
526 11
        return section_config
527

528 11
    def __repr__(self):
529 0
        return (
530
            'SectionConfigProvider(section_name=%s, '
531
            'session=%s, override_providers=%s)' % (
532
                self._section_name, self._session,
533
                self._override_providers,
534
            )
535
        )
536

537

538 11
class ConstantProvider(BaseProvider):
539
    """This provider provides a constant value."""
540 11
    def __init__(self, value):
541 11
        self._value = value
542

543 11
    def provide(self):
544
        """Provide the constant value given during initialization."""
545 11
        return self._value
546

547 11
    def __repr__(self):
548 0
        return 'ConstantProvider(value=%s)' % self._value

Read our documentation on viewing source code .

Loading