Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/configprovider.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

332 statements  

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"""This module contains the interface for controlling how configuration 

14is loaded. 

15""" 

16 

17import copy 

18import logging 

19import os 

20 

21from botocore import utils 

22from botocore.exceptions import InvalidConfigError 

23 

24logger = logging.getLogger(__name__) 

25 

26 

27#: A default dictionary that maps the logical names for session variables 

28#: to the specific environment variables and configuration file names 

29#: that contain the values for these variables. 

30#: When creating a new Session object, you can pass in your own dictionary 

31#: to remap the logical names or to add new logical names. You can then 

32#: get the current value for these variables by using the 

33#: ``get_config_variable`` method of the :class:`botocore.session.Session` 

34#: class. 

35#: These form the keys of the dictionary. The values in the dictionary 

36#: are tuples of (<config_name>, <environment variable>, <default value>, 

37#: <conversion func>). 

38#: The conversion func is a function that takes the configuration value 

39#: as an argument and returns the converted value. If this value is 

40#: None, then the configuration value is returned unmodified. This 

41#: conversion function can be used to type convert config values to 

42#: values other than the default values of strings. 

43#: The ``profile`` and ``config_file`` variables should always have a 

44#: None value for the first entry in the tuple because it doesn't make 

45#: sense to look inside the config file for the location of the config 

46#: file or for the default profile to use. 

47#: The ``config_name`` is the name to look for in the configuration file, 

48#: the ``env var`` is the OS environment variable (``os.environ``) to 

49#: use, and ``default_value`` is the value to use if no value is otherwise 

50#: found. 

51#: NOTE: Fixing the spelling of this variable would be a breaking change. 

52#: Please leave as is. 

53BOTOCORE_DEFAUT_SESSION_VARIABLES = { 

54 # logical: config_file, env_var, default_value, conversion_func 

55 'profile': (None, ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE'], None, None), 

56 'region': ('region', 'AWS_DEFAULT_REGION', None, None), 

57 'data_path': ('data_path', 'AWS_DATA_PATH', None, None), 

58 'config_file': (None, 'AWS_CONFIG_FILE', '~/.aws/config', None), 

59 'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None), 

60 'api_versions': ('api_versions', None, {}, None), 

61 # This is the shared credentials file amongst sdks. 

62 'credentials_file': ( 

63 None, 

64 'AWS_SHARED_CREDENTIALS_FILE', 

65 '~/.aws/credentials', 

66 None, 

67 ), 

68 # These variables only exist in the config file. 

69 # This is the number of seconds until we time out a request to 

70 # the instance metadata service. 

71 'metadata_service_timeout': ( 

72 'metadata_service_timeout', 

73 'AWS_METADATA_SERVICE_TIMEOUT', 

74 1, 

75 int, 

76 ), 

77 # This is the number of request attempts we make until we give 

78 # up trying to retrieve data from the instance metadata service. 

79 'metadata_service_num_attempts': ( 

80 'metadata_service_num_attempts', 

81 'AWS_METADATA_SERVICE_NUM_ATTEMPTS', 

82 1, 

83 int, 

84 ), 

85 'ec2_metadata_service_endpoint': ( 

86 'ec2_metadata_service_endpoint', 

87 'AWS_EC2_METADATA_SERVICE_ENDPOINT', 

88 None, 

89 None, 

90 ), 

91 'ec2_metadata_service_endpoint_mode': ( 

92 'ec2_metadata_service_endpoint_mode', 

93 'AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE', 

94 None, 

95 None, 

96 ), 

97 'ec2_metadata_v1_disabled': ( 

98 'ec2_metadata_v1_disabled', 

99 'AWS_EC2_METADATA_V1_DISABLED', 

100 False, 

101 utils.ensure_boolean, 

102 ), 

103 'imds_use_ipv6': ( 

104 'imds_use_ipv6', 

105 'AWS_IMDS_USE_IPV6', 

106 False, 

107 utils.ensure_boolean, 

108 ), 

109 'use_dualstack_endpoint': ( 

110 'use_dualstack_endpoint', 

111 'AWS_USE_DUALSTACK_ENDPOINT', 

112 None, 

113 utils.ensure_boolean, 

114 ), 

115 'use_fips_endpoint': ( 

116 'use_fips_endpoint', 

117 'AWS_USE_FIPS_ENDPOINT', 

118 None, 

119 utils.ensure_boolean, 

120 ), 

121 'ignore_configured_endpoint_urls': ( 

122 'ignore_configured_endpoint_urls', 

123 'AWS_IGNORE_CONFIGURED_ENDPOINT_URLS', 

124 None, 

125 utils.ensure_boolean, 

126 ), 

127 'parameter_validation': ('parameter_validation', None, True, None), 

128 # Client side monitoring configurations. 

129 # Note: These configurations are considered internal to botocore. 

130 # Do not use them until publicly documented. 

131 'csm_enabled': ( 

132 'csm_enabled', 

133 'AWS_CSM_ENABLED', 

134 False, 

135 utils.ensure_boolean, 

136 ), 

137 'csm_host': ('csm_host', 'AWS_CSM_HOST', '127.0.0.1', None), 

138 'csm_port': ('csm_port', 'AWS_CSM_PORT', 31000, int), 

139 'csm_client_id': ('csm_client_id', 'AWS_CSM_CLIENT_ID', '', None), 

140 # Endpoint discovery configuration 

141 'endpoint_discovery_enabled': ( 

142 'endpoint_discovery_enabled', 

143 'AWS_ENDPOINT_DISCOVERY_ENABLED', 

144 'auto', 

145 None, 

146 ), 

147 'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', 'legacy', None), 

148 'defaults_mode': ('defaults_mode', 'AWS_DEFAULTS_MODE', 'legacy', None), 

149 # We can't have a default here for v1 because we need to defer to 

150 # whatever the defaults are in _retry.json. 

151 'max_attempts': ('max_attempts', 'AWS_MAX_ATTEMPTS', None, int), 

152 'user_agent_appid': ('sdk_ua_app_id', 'AWS_SDK_UA_APP_ID', None, None), 

153 'request_min_compression_size_bytes': ( 

154 'request_min_compression_size_bytes', 

155 'AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES', 

156 10240, 

157 None, 

158 ), 

159 'disable_request_compression': ( 

160 'disable_request_compression', 

161 'AWS_DISABLE_REQUEST_COMPRESSION', 

162 False, 

163 utils.ensure_boolean, 

164 ), 

165 'sigv4a_signing_region_set': ( 

166 'sigv4a_signing_region_set', 

167 'AWS_SIGV4A_SIGNING_REGION_SET', 

168 None, 

169 None, 

170 ), 

171} 

172 

173# Evaluate AWS_STS_REGIONAL_ENDPOINTS settings 

174try: 

175 # This is not a public interface and is subject to abrupt breaking changes. 

176 # Any usage is not advised or supported in external code bases. 

177 from botocore.customizations.sts import ( 

178 sts_default_setting as _sts_default_setting, 

179 ) 

180except ImportError: 

181 _sts_default_setting = 'legacy' 

182 

183_STS_DEFAULT_SETTINGS = { 

184 'sts_regional_endpoints': ( 

185 'sts_regional_endpoints', 

186 'AWS_STS_REGIONAL_ENDPOINTS', 

187 _sts_default_setting, 

188 None, 

189 ), 

190} 

191BOTOCORE_DEFAUT_SESSION_VARIABLES.update(_STS_DEFAULT_SETTINGS) 

192 

193 

194# A mapping for the s3 specific configuration vars. These are the configuration 

195# vars that typically go in the s3 section of the config file. This mapping 

196# follows the same schema as the previous session variable mapping. 

197DEFAULT_S3_CONFIG_VARS = { 

198 'addressing_style': (('s3', 'addressing_style'), None, None, None), 

199 'use_accelerate_endpoint': ( 

200 ('s3', 'use_accelerate_endpoint'), 

201 None, 

202 None, 

203 utils.ensure_boolean, 

204 ), 

205 'use_dualstack_endpoint': ( 

206 ('s3', 'use_dualstack_endpoint'), 

207 None, 

208 None, 

209 utils.ensure_boolean, 

210 ), 

211 'payload_signing_enabled': ( 

212 ('s3', 'payload_signing_enabled'), 

213 None, 

214 None, 

215 utils.ensure_boolean, 

216 ), 

217 'use_arn_region': ( 

218 ['s3_use_arn_region', ('s3', 'use_arn_region')], 

219 'AWS_S3_USE_ARN_REGION', 

220 None, 

221 utils.ensure_boolean, 

222 ), 

223 'us_east_1_regional_endpoint': ( 

224 [ 

225 's3_us_east_1_regional_endpoint', 

226 ('s3', 'us_east_1_regional_endpoint'), 

227 ], 

228 'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT', 

229 None, 

230 None, 

231 ), 

232 's3_disable_multiregion_access_points': ( 

233 ('s3', 's3_disable_multiregion_access_points'), 

234 'AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS', 

235 None, 

236 utils.ensure_boolean, 

237 ), 

238} 

239# A mapping for the proxy specific configuration vars. These are 

240# used to configure how botocore interacts with proxy setups while 

241# sending requests. 

242DEFAULT_PROXIES_CONFIG_VARS = { 

243 'proxy_ca_bundle': ('proxy_ca_bundle', None, None, None), 

244 'proxy_client_cert': ('proxy_client_cert', None, None, None), 

245 'proxy_use_forwarding_for_https': ( 

246 'proxy_use_forwarding_for_https', 

247 None, 

248 None, 

249 utils.normalize_boolean, 

250 ), 

251} 

252 

253 

254def create_botocore_default_config_mapping(session): 

255 chain_builder = ConfigChainFactory(session=session) 

256 config_mapping = _create_config_chain_mapping( 

257 chain_builder, BOTOCORE_DEFAUT_SESSION_VARIABLES 

258 ) 

259 config_mapping['s3'] = SectionConfigProvider( 

260 's3', 

261 session, 

262 _create_config_chain_mapping(chain_builder, DEFAULT_S3_CONFIG_VARS), 

263 ) 

264 config_mapping['proxies_config'] = SectionConfigProvider( 

265 'proxies_config', 

266 session, 

267 _create_config_chain_mapping( 

268 chain_builder, DEFAULT_PROXIES_CONFIG_VARS 

269 ), 

270 ) 

271 return config_mapping 

272 

273 

274def _create_config_chain_mapping(chain_builder, config_variables): 

275 mapping = {} 

276 for logical_name, config in config_variables.items(): 

277 mapping[logical_name] = chain_builder.create_config_chain( 

278 instance_name=logical_name, 

279 env_var_names=config[1], 

280 config_property_names=config[0], 

281 default=config[2], 

282 conversion_func=config[3], 

283 ) 

284 return mapping 

285 

286 

287class DefaultConfigResolver: 

288 def __init__(self, default_config_data): 

289 self._base_default_config = default_config_data['base'] 

290 self._modes = default_config_data['modes'] 

291 self._resolved_default_configurations = {} 

292 

293 def _resolve_default_values_by_mode(self, mode): 

294 default_config = self._base_default_config.copy() 

295 modifications = self._modes.get(mode) 

296 

297 for config_var in modifications: 

298 default_value = default_config[config_var] 

299 modification_dict = modifications[config_var] 

300 modification = list(modification_dict.keys())[0] 

301 modification_value = modification_dict[modification] 

302 if modification == 'multiply': 

303 default_value *= modification_value 

304 elif modification == 'add': 

305 default_value += modification_value 

306 elif modification == 'override': 

307 default_value = modification_value 

308 default_config[config_var] = default_value 

309 return default_config 

310 

311 def get_default_modes(self): 

312 default_modes = ['legacy', 'auto'] 

313 default_modes.extend(self._modes.keys()) 

314 return default_modes 

315 

316 def get_default_config_values(self, mode): 

317 if mode not in self._resolved_default_configurations: 

318 defaults = self._resolve_default_values_by_mode(mode) 

319 self._resolved_default_configurations[mode] = defaults 

320 return self._resolved_default_configurations[mode] 

321 

322 

323class ConfigChainFactory: 

324 """Factory class to create our most common configuration chain case. 

325 

326 This is a convenience class to construct configuration chains that follow 

327 our most common pattern. This is to prevent ordering them incorrectly, 

328 and to make the config chain construction more readable. 

329 """ 

330 

331 def __init__(self, session, environ=None): 

332 """Initialize a ConfigChainFactory. 

333 

334 :type session: :class:`botocore.session.Session` 

335 :param session: This is the session that should be used to look up 

336 values from the config file. 

337 

338 :type environ: dict 

339 :param environ: A mapping to use for environment variables. If this 

340 is not provided it will default to use os.environ. 

341 """ 

342 self._session = session 

343 if environ is None: 

344 environ = os.environ 

345 self._environ = environ 

346 

347 def create_config_chain( 

348 self, 

349 instance_name=None, 

350 env_var_names=None, 

351 config_property_names=None, 

352 default=None, 

353 conversion_func=None, 

354 ): 

355 """Build a config chain following the standard botocore pattern. 

356 

357 In botocore most of our config chains follow the the precendence: 

358 session_instance_variables, environment, config_file, default_value. 

359 

360 This is a convenience function for creating a chain that follow 

361 that precendence. 

362 

363 :type instance_name: str 

364 :param instance_name: This indicates what session instance variable 

365 corresponds to this config value. If it is None it will not be 

366 added to the chain. 

367 

368 :type env_var_names: str or list of str or None 

369 :param env_var_names: One or more environment variable names to 

370 search for this value. They are searched in order. If it is None 

371 it will not be added to the chain. 

372 

373 :type config_property_names: str/tuple or list of str/tuple or None 

374 :param config_property_names: One of more strings or tuples 

375 representing the name of the key in the config file for this 

376 config option. They are searched in order. If it is None it will 

377 not be added to the chain. 

378 

379 :type default: Any 

380 :param default: Any constant value to be returned. 

381 

382 :type conversion_func: None or callable 

383 :param conversion_func: If this value is None then it has no effect on 

384 the return type. Otherwise, it is treated as a function that will 

385 conversion_func our provided type. 

386 

387 :rvalue: ConfigChain 

388 :returns: A ConfigChain that resolves in the order env_var_names -> 

389 config_property_name -> default. Any values that were none are 

390 omitted form the chain. 

391 """ 

392 providers = [] 

393 if instance_name is not None: 

394 providers.append( 

395 InstanceVarProvider( 

396 instance_var=instance_name, session=self._session 

397 ) 

398 ) 

399 if env_var_names is not None: 

400 providers.extend(self._get_env_providers(env_var_names)) 

401 if config_property_names is not None: 

402 providers.extend( 

403 self._get_scoped_config_providers(config_property_names) 

404 ) 

405 if default is not None: 

406 providers.append(ConstantProvider(value=default)) 

407 

408 return ChainProvider( 

409 providers=providers, 

410 conversion_func=conversion_func, 

411 ) 

412 

413 def _get_env_providers(self, env_var_names): 

414 env_var_providers = [] 

415 if not isinstance(env_var_names, list): 

416 env_var_names = [env_var_names] 

417 for env_var_name in env_var_names: 

418 env_var_providers.append( 

419 EnvironmentProvider(name=env_var_name, env=self._environ) 

420 ) 

421 return env_var_providers 

422 

423 def _get_scoped_config_providers(self, config_property_names): 

424 scoped_config_providers = [] 

425 if not isinstance(config_property_names, list): 

426 config_property_names = [config_property_names] 

427 for config_property_name in config_property_names: 

428 scoped_config_providers.append( 

429 ScopedConfigProvider( 

430 config_var_name=config_property_name, 

431 session=self._session, 

432 ) 

433 ) 

434 return scoped_config_providers 

435 

436 

437class ConfigValueStore: 

438 """The ConfigValueStore object stores configuration values.""" 

439 

440 def __init__(self, mapping=None): 

441 """Initialize a ConfigValueStore. 

442 

443 :type mapping: dict 

444 :param mapping: The mapping parameter is a map of string to a subclass 

445 of BaseProvider. When a config variable is asked for via the 

446 get_config_variable method, the corresponding provider will be 

447 invoked to load the value. 

448 """ 

449 self._overrides = {} 

450 self._mapping = {} 

451 if mapping is not None: 

452 for logical_name, provider in mapping.items(): 

453 self.set_config_provider(logical_name, provider) 

454 

455 def __deepcopy__(self, memo): 

456 config_store = ConfigValueStore(copy.deepcopy(self._mapping, memo)) 

457 for logical_name, override_value in self._overrides.items(): 

458 config_store.set_config_variable(logical_name, override_value) 

459 

460 return config_store 

461 

462 def __copy__(self): 

463 config_store = ConfigValueStore(copy.copy(self._mapping)) 

464 for logical_name, override_value in self._overrides.items(): 

465 config_store.set_config_variable(logical_name, override_value) 

466 

467 return config_store 

468 

469 def get_config_variable(self, logical_name): 

470 """ 

471 Retrieve the value associeated with the specified logical_name 

472 from the corresponding provider. If no value is found None will 

473 be returned. 

474 

475 :type logical_name: str 

476 :param logical_name: The logical name of the session variable 

477 you want to retrieve. This name will be mapped to the 

478 appropriate environment variable name for this session as 

479 well as the appropriate config file entry. 

480 

481 :returns: value of variable or None if not defined. 

482 """ 

483 if logical_name in self._overrides: 

484 return self._overrides[logical_name] 

485 if logical_name not in self._mapping: 

486 return None 

487 provider = self._mapping[logical_name] 

488 return provider.provide() 

489 

490 def get_config_provider(self, logical_name): 

491 """ 

492 Retrieve the provider associated with the specified logical_name. 

493 If no provider is found None will be returned. 

494 

495 :type logical_name: str 

496 :param logical_name: The logical name of the session variable 

497 you want to retrieve. This name will be mapped to the 

498 appropriate environment variable name for this session as 

499 well as the appropriate config file entry. 

500 

501 :returns: configuration provider or None if not defined. 

502 """ 

503 if ( 

504 logical_name in self._overrides 

505 or logical_name not in self._mapping 

506 ): 

507 return None 

508 provider = self._mapping[logical_name] 

509 return provider 

510 

511 def set_config_variable(self, logical_name, value): 

512 """Set a configuration variable to a specific value. 

513 

514 By using this method, you can override the normal lookup 

515 process used in ``get_config_variable`` by explicitly setting 

516 a value. Subsequent calls to ``get_config_variable`` will 

517 use the ``value``. This gives you per-session specific 

518 configuration values. 

519 

520 :: 

521 >>> # Assume logical name 'foo' maps to env var 'FOO' 

522 >>> os.environ['FOO'] = 'myvalue' 

523 >>> s.get_config_variable('foo') 

524 'myvalue' 

525 >>> s.set_config_variable('foo', 'othervalue') 

526 >>> s.get_config_variable('foo') 

527 'othervalue' 

528 

529 :type logical_name: str 

530 :param logical_name: The logical name of the session variable 

531 you want to set. These are the keys in ``SESSION_VARIABLES``. 

532 

533 :param value: The value to associate with the config variable. 

534 """ 

535 self._overrides[logical_name] = value 

536 

537 def clear_config_variable(self, logical_name): 

538 """Remove an override config variable from the session. 

539 

540 :type logical_name: str 

541 :param logical_name: The name of the parameter to clear the override 

542 value from. 

543 """ 

544 self._overrides.pop(logical_name, None) 

545 

546 def set_config_provider(self, logical_name, provider): 

547 """Set the provider for a config value. 

548 

549 This provides control over how a particular configuration value is 

550 loaded. This replaces the provider for ``logical_name`` with the new 

551 ``provider``. 

552 

553 :type logical_name: str 

554 :param logical_name: The name of the config value to change the config 

555 provider for. 

556 

557 :type provider: :class:`botocore.configprovider.BaseProvider` 

558 :param provider: The new provider that should be responsible for 

559 providing a value for the config named ``logical_name``. 

560 """ 

561 self._mapping[logical_name] = provider 

562 

563 

564class SmartDefaultsConfigStoreFactory: 

565 def __init__(self, default_config_resolver, imds_region_provider): 

566 self._default_config_resolver = default_config_resolver 

567 self._imds_region_provider = imds_region_provider 

568 # Initializing _instance_metadata_region as None so we 

569 # can fetch region in a lazy fashion only when needed. 

570 self._instance_metadata_region = None 

571 

572 def merge_smart_defaults(self, config_store, mode, region_name): 

573 if mode == 'auto': 

574 mode = self.resolve_auto_mode(region_name) 

575 default_configs = ( 

576 self._default_config_resolver.get_default_config_values(mode) 

577 ) 

578 for config_var in default_configs: 

579 config_value = default_configs[config_var] 

580 method = getattr(self, f'_set_{config_var}', None) 

581 if method: 

582 method(config_store, config_value) 

583 

584 def resolve_auto_mode(self, region_name): 

585 current_region = None 

586 if os.environ.get('AWS_EXECUTION_ENV'): 

587 default_region = os.environ.get('AWS_DEFAULT_REGION') 

588 current_region = os.environ.get('AWS_REGION', default_region) 

589 if not current_region: 

590 if self._instance_metadata_region: 

591 current_region = self._instance_metadata_region 

592 else: 

593 try: 

594 current_region = self._imds_region_provider.provide() 

595 self._instance_metadata_region = current_region 

596 except Exception: 

597 pass 

598 

599 if current_region: 

600 if region_name == current_region: 

601 return 'in-region' 

602 else: 

603 return 'cross-region' 

604 return 'standard' 

605 

606 def _update_provider(self, config_store, variable, value): 

607 original_provider = config_store.get_config_provider(variable) 

608 default_provider = ConstantProvider(value) 

609 if isinstance(original_provider, ChainProvider): 

610 chain_provider_copy = copy.deepcopy(original_provider) 

611 chain_provider_copy.set_default_provider(default_provider) 

612 default_provider = chain_provider_copy 

613 elif isinstance(original_provider, BaseProvider): 

614 default_provider = ChainProvider( 

615 providers=[original_provider, default_provider] 

616 ) 

617 config_store.set_config_provider(variable, default_provider) 

618 

619 def _update_section_provider( 

620 self, config_store, section_name, variable, value 

621 ): 

622 section_provider_copy = copy.deepcopy( 

623 config_store.get_config_provider(section_name) 

624 ) 

625 section_provider_copy.set_default_provider( 

626 variable, ConstantProvider(value) 

627 ) 

628 config_store.set_config_provider(section_name, section_provider_copy) 

629 

630 def _set_retryMode(self, config_store, value): 

631 self._update_provider(config_store, 'retry_mode', value) 

632 

633 def _set_stsRegionalEndpoints(self, config_store, value): 

634 self._update_provider(config_store, 'sts_regional_endpoints', value) 

635 

636 def _set_s3UsEast1RegionalEndpoints(self, config_store, value): 

637 self._update_section_provider( 

638 config_store, 's3', 'us_east_1_regional_endpoint', value 

639 ) 

640 

641 def _set_connectTimeoutInMillis(self, config_store, value): 

642 self._update_provider(config_store, 'connect_timeout', value / 1000) 

643 

644 

645class BaseProvider: 

646 """Base class for configuration value providers. 

647 

648 A configuration provider has some method of providing a configuration 

649 value. 

650 """ 

651 

652 def provide(self): 

653 """Provide a config value.""" 

654 raise NotImplementedError('provide') 

655 

656 

657class ChainProvider(BaseProvider): 

658 """This provider wraps one or more other providers. 

659 

660 Each provider in the chain is called, the first one returning a non-None 

661 value is then returned. 

662 """ 

663 

664 def __init__(self, providers=None, conversion_func=None): 

665 """Initalize a ChainProvider. 

666 

667 :type providers: list 

668 :param providers: The initial list of providers to check for values 

669 when invoked. 

670 

671 :type conversion_func: None or callable 

672 :param conversion_func: If this value is None then it has no affect on 

673 the return type. Otherwise, it is treated as a function that will 

674 transform provided value. 

675 """ 

676 if providers is None: 

677 providers = [] 

678 self._providers = providers 

679 self._conversion_func = conversion_func 

680 

681 def __deepcopy__(self, memo): 

682 return ChainProvider( 

683 copy.deepcopy(self._providers, memo), self._conversion_func 

684 ) 

685 

686 def provide(self): 

687 """Provide the value from the first provider to return non-None. 

688 

689 Each provider in the chain has its provide method called. The first 

690 one in the chain to return a non-None value is the returned from the 

691 ChainProvider. When no non-None value is found, None is returned. 

692 """ 

693 for provider in self._providers: 

694 value = provider.provide() 

695 if value is not None: 

696 return self._convert_type(value) 

697 return None 

698 

699 def set_default_provider(self, default_provider): 

700 if self._providers and isinstance( 

701 self._providers[-1], ConstantProvider 

702 ): 

703 self._providers[-1] = default_provider 

704 else: 

705 self._providers.append(default_provider) 

706 

707 num_of_constants = sum( 

708 isinstance(provider, ConstantProvider) 

709 for provider in self._providers 

710 ) 

711 if num_of_constants > 1: 

712 logger.info( 

713 'ChainProvider object contains multiple ' 

714 'instances of ConstantProvider objects' 

715 ) 

716 

717 def _convert_type(self, value): 

718 if self._conversion_func is not None: 

719 return self._conversion_func(value) 

720 return value 

721 

722 def __repr__(self): 

723 return '[{}]'.format(', '.join([str(p) for p in self._providers])) 

724 

725 

726class InstanceVarProvider(BaseProvider): 

727 """This class loads config values from the session instance vars.""" 

728 

729 def __init__(self, instance_var, session): 

730 """Initialize InstanceVarProvider. 

731 

732 :type instance_var: str 

733 :param instance_var: The instance variable to load from the session. 

734 

735 :type session: :class:`botocore.session.Session` 

736 :param session: The botocore session to get the loaded configuration 

737 file variables from. 

738 """ 

739 self._instance_var = instance_var 

740 self._session = session 

741 

742 def __deepcopy__(self, memo): 

743 return InstanceVarProvider( 

744 copy.deepcopy(self._instance_var, memo), self._session 

745 ) 

746 

747 def provide(self): 

748 """Provide a config value from the session instance vars.""" 

749 instance_vars = self._session.instance_variables() 

750 value = instance_vars.get(self._instance_var) 

751 return value 

752 

753 def __repr__(self): 

754 return f'InstanceVarProvider(instance_var={self._instance_var}, session={self._session})' 

755 

756 

757class ScopedConfigProvider(BaseProvider): 

758 def __init__(self, config_var_name, session): 

759 """Initialize ScopedConfigProvider. 

760 

761 :type config_var_name: str or tuple 

762 :param config_var_name: The name of the config variable to load from 

763 the configuration file. If the value is a tuple, it must only 

764 consist of two items, where the first item represents the section 

765 and the second item represents the config var name in the section. 

766 

767 :type session: :class:`botocore.session.Session` 

768 :param session: The botocore session to get the loaded configuration 

769 file variables from. 

770 """ 

771 self._config_var_name = config_var_name 

772 self._session = session 

773 

774 def __deepcopy__(self, memo): 

775 return ScopedConfigProvider( 

776 copy.deepcopy(self._config_var_name, memo), self._session 

777 ) 

778 

779 def provide(self): 

780 """Provide a value from a config file property.""" 

781 scoped_config = self._session.get_scoped_config() 

782 if isinstance(self._config_var_name, tuple): 

783 section_config = scoped_config.get(self._config_var_name[0]) 

784 if not isinstance(section_config, dict): 

785 return None 

786 return section_config.get(self._config_var_name[1]) 

787 return scoped_config.get(self._config_var_name) 

788 

789 def __repr__(self): 

790 return f'ScopedConfigProvider(config_var_name={self._config_var_name}, session={self._session})' 

791 

792 

793class EnvironmentProvider(BaseProvider): 

794 """This class loads config values from environment variables.""" 

795 

796 def __init__(self, name, env): 

797 """Initialize with the keys in the dictionary to check. 

798 

799 :type name: str 

800 :param name: The key with that name will be loaded and returned. 

801 

802 :type env: dict 

803 :param env: Environment variables dictionary to get variables from. 

804 """ 

805 self._name = name 

806 self._env = env 

807 

808 def __deepcopy__(self, memo): 

809 return EnvironmentProvider( 

810 copy.deepcopy(self._name, memo), copy.deepcopy(self._env, memo) 

811 ) 

812 

813 def provide(self): 

814 """Provide a config value from a source dictionary.""" 

815 if self._name in self._env: 

816 return self._env[self._name] 

817 return None 

818 

819 def __repr__(self): 

820 return f'EnvironmentProvider(name={self._name}, env={self._env})' 

821 

822 

823class SectionConfigProvider(BaseProvider): 

824 """Provides a dictionary from a section in the scoped config 

825 

826 This is useful for retrieving scoped config variables (i.e. s3) that have 

827 their own set of config variables and resolving logic. 

828 """ 

829 

830 def __init__(self, section_name, session, override_providers=None): 

831 self._section_name = section_name 

832 self._session = session 

833 self._scoped_config_provider = ScopedConfigProvider( 

834 self._section_name, self._session 

835 ) 

836 self._override_providers = override_providers 

837 if self._override_providers is None: 

838 self._override_providers = {} 

839 

840 def __deepcopy__(self, memo): 

841 return SectionConfigProvider( 

842 copy.deepcopy(self._section_name, memo), 

843 self._session, 

844 copy.deepcopy(self._override_providers, memo), 

845 ) 

846 

847 def provide(self): 

848 section_config = self._scoped_config_provider.provide() 

849 if section_config and not isinstance(section_config, dict): 

850 logger.debug( 

851 "The %s config key is not a dictionary type, " 

852 "ignoring its value of: %s", 

853 self._section_name, 

854 section_config, 

855 ) 

856 return None 

857 for section_config_var, provider in self._override_providers.items(): 

858 provider_val = provider.provide() 

859 if provider_val is not None: 

860 if section_config is None: 

861 section_config = {} 

862 section_config[section_config_var] = provider_val 

863 return section_config 

864 

865 def set_default_provider(self, key, default_provider): 

866 provider = self._override_providers.get(key) 

867 if isinstance(provider, ChainProvider): 

868 provider.set_default_provider(default_provider) 

869 return 

870 elif isinstance(provider, BaseProvider): 

871 default_provider = ChainProvider( 

872 providers=[provider, default_provider] 

873 ) 

874 self._override_providers[key] = default_provider 

875 

876 def __repr__(self): 

877 return ( 

878 f'SectionConfigProvider(section_name={self._section_name}, ' 

879 f'session={self._session}, ' 

880 f'override_providers={self._override_providers})' 

881 ) 

882 

883 

884class ConstantProvider(BaseProvider): 

885 """This provider provides a constant value.""" 

886 

887 def __init__(self, value): 

888 self._value = value 

889 

890 def __deepcopy__(self, memo): 

891 return ConstantProvider(copy.deepcopy(self._value, memo)) 

892 

893 def provide(self): 

894 """Provide the constant value given during initialization.""" 

895 return self._value 

896 

897 def __repr__(self): 

898 return f'ConstantProvider(value={self._value})' 

899 

900 

901class ConfiguredEndpointProvider(BaseProvider): 

902 """Lookup an endpoint URL from environment variable or shared config file. 

903 

904 NOTE: This class is considered private and is subject to abrupt breaking 

905 changes or removal without prior announcement. Please do not use it 

906 directly. 

907 """ 

908 

909 _ENDPOINT_URL_LOOKUP_ORDER = [ 

910 'environment_service', 

911 'environment_global', 

912 'config_service', 

913 'config_global', 

914 ] 

915 

916 def __init__( 

917 self, 

918 full_config, 

919 scoped_config, 

920 client_name, 

921 environ=None, 

922 ): 

923 """Initialize a ConfiguredEndpointProviderChain. 

924 

925 :type full_config: dict 

926 :param full_config: This is the dict representing the full 

927 configuration file. 

928 

929 :type scoped_config: dict 

930 :param scoped_config: This is the dict representing the configuration 

931 for the current profile for the session. 

932 

933 :type client_name: str 

934 :param client_name: The name used to instantiate a client using 

935 botocore.session.Session.create_client. 

936 

937 :type environ: dict 

938 :param environ: A mapping to use for environment variables. If this 

939 is not provided it will default to use os.environ. 

940 """ 

941 self._full_config = full_config 

942 self._scoped_config = scoped_config 

943 self._client_name = client_name 

944 self._transformed_service_id = self._get_snake_case_service_id( 

945 self._client_name 

946 ) 

947 if environ is None: 

948 environ = os.environ 

949 self._environ = environ 

950 

951 def provide(self): 

952 """Lookup the configured endpoint URL. 

953 

954 The order is: 

955 

956 1. The value provided by a service-specific environment variable. 

957 2. The value provided by the global endpoint environment variable 

958 (AWS_ENDPOINT_URL). 

959 3. The value provided by a service-specific parameter from a services 

960 definition section in the shared configuration file. 

961 4. The value provided by the global parameter from a services 

962 definition section in the shared configuration file. 

963 """ 

964 for location in self._ENDPOINT_URL_LOOKUP_ORDER: 

965 logger.debug( 

966 'Looking for endpoint for %s via: %s', 

967 self._client_name, 

968 location, 

969 ) 

970 

971 endpoint_url = getattr(self, f'_get_endpoint_url_{location}')() 

972 

973 if endpoint_url: 

974 logger.info( 

975 'Found endpoint for %s via: %s.', 

976 self._client_name, 

977 location, 

978 ) 

979 return endpoint_url 

980 

981 logger.debug('No configured endpoint found.') 

982 return None 

983 

984 def _get_snake_case_service_id(self, client_name): 

985 # Get the service ID without loading the service data file, accounting 

986 # for any aliases and standardizing the names with hyphens. 

987 client_name = utils.SERVICE_NAME_ALIASES.get(client_name, client_name) 

988 hyphenized_service_id = ( 

989 utils.CLIENT_NAME_TO_HYPHENIZED_SERVICE_ID_OVERRIDES.get( 

990 client_name, client_name 

991 ) 

992 ) 

993 return hyphenized_service_id.replace('-', '_') 

994 

995 def _get_service_env_var_name(self): 

996 transformed_service_id_env = self._transformed_service_id.upper() 

997 return f'AWS_ENDPOINT_URL_{transformed_service_id_env}' 

998 

999 def _get_services_config(self): 

1000 if 'services' not in self._scoped_config: 

1001 return {} 

1002 

1003 section_name = self._scoped_config['services'] 

1004 services_section = self._full_config.get('services', {}).get( 

1005 section_name 

1006 ) 

1007 

1008 if not services_section: 

1009 error_msg = ( 

1010 f'The profile is configured to use the services ' 

1011 f'section but the "{section_name}" services ' 

1012 f'configuration does not exist.' 

1013 ) 

1014 raise InvalidConfigError(error_msg=error_msg) 

1015 

1016 return services_section 

1017 

1018 def _get_endpoint_url_config_service(self): 

1019 snakecase_service_id = self._transformed_service_id.lower() 

1020 return ( 

1021 self._get_services_config() 

1022 .get(snakecase_service_id, {}) 

1023 .get('endpoint_url') 

1024 ) 

1025 

1026 def _get_endpoint_url_config_global(self): 

1027 return self._scoped_config.get('endpoint_url') 

1028 

1029 def _get_endpoint_url_environment_service(self): 

1030 return EnvironmentProvider( 

1031 name=self._get_service_env_var_name(), env=self._environ 

1032 ).provide() 

1033 

1034 def _get_endpoint_url_environment_global(self): 

1035 return EnvironmentProvider( 

1036 name='AWS_ENDPOINT_URL', env=self._environ 

1037 ).provide()