Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/botocore/args.py: 17%
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
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
1# Copyright 2016 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"""Internal module to help with normalizing botocore client args.
15This module (and all function/classes within this module) should be
16considered internal, and *not* a public API.
18"""
20import copy
21import logging
22import socket
24import botocore.exceptions
25import botocore.parsers
26import botocore.serialize
27from botocore.config import Config
28from botocore.endpoint import EndpointCreator
29from botocore.regions import EndpointResolverBuiltins as EPRBuiltins
30from botocore.regions import EndpointRulesetResolver
31from botocore.signers import RequestSigner
32from botocore.useragent import UserAgentString, register_feature_id
33from botocore.utils import (
34 PRIORITY_ORDERED_SUPPORTED_PROTOCOLS, # noqa: F401
35 ensure_boolean,
36 is_s3_accelerate_url,
37)
39logger = logging.getLogger(__name__)
42VALID_REGIONAL_ENDPOINTS_CONFIG = [
43 'legacy',
44 'regional',
45]
46LEGACY_GLOBAL_STS_REGIONS = [
47 'ap-northeast-1',
48 'ap-south-1',
49 'ap-southeast-1',
50 'ap-southeast-2',
51 'aws-global',
52 'ca-central-1',
53 'eu-central-1',
54 'eu-north-1',
55 'eu-west-1',
56 'eu-west-2',
57 'eu-west-3',
58 'sa-east-1',
59 'us-east-1',
60 'us-east-2',
61 'us-west-1',
62 'us-west-2',
63]
64# Maximum allowed length of the ``user_agent_appid`` config field. Longer
65# values result in a warning-level log message.
66USERAGENT_APPID_MAXLEN = 50
68VALID_REQUEST_CHECKSUM_CALCULATION_CONFIG = (
69 "when_supported",
70 "when_required",
71)
72VALID_RESPONSE_CHECKSUM_VALIDATION_CONFIG = (
73 "when_supported",
74 "when_required",
75)
78VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG = (
79 'preferred',
80 'disabled',
81 'required',
82)
85class ClientArgsCreator:
86 def __init__(
87 self,
88 event_emitter,
89 user_agent,
90 response_parser_factory,
91 loader,
92 exceptions_factory,
93 config_store,
94 user_agent_creator=None,
95 ):
96 self._event_emitter = event_emitter
97 self._response_parser_factory = response_parser_factory
98 self._loader = loader
99 self._exceptions_factory = exceptions_factory
100 self._config_store = config_store
101 if user_agent_creator is None:
102 self._session_ua_creator = UserAgentString.from_environment()
103 else:
104 self._session_ua_creator = user_agent_creator
106 def get_client_args(
107 self,
108 service_model,
109 region_name,
110 is_secure,
111 endpoint_url,
112 verify,
113 credentials,
114 scoped_config,
115 client_config,
116 endpoint_bridge,
117 auth_token=None,
118 endpoints_ruleset_data=None,
119 partition_data=None,
120 ):
121 final_args = self.compute_client_args(
122 service_model,
123 client_config,
124 endpoint_bridge,
125 region_name,
126 endpoint_url,
127 is_secure,
128 scoped_config,
129 )
131 service_name = final_args['service_name'] # noqa
132 parameter_validation = final_args['parameter_validation']
133 endpoint_config = final_args['endpoint_config']
134 protocol = final_args['protocol']
135 config_kwargs = final_args['config_kwargs']
136 s3_config = final_args['s3_config']
137 partition = endpoint_config['metadata'].get('partition', None)
138 socket_options = final_args['socket_options']
139 configured_endpoint_url = final_args['configured_endpoint_url']
140 signing_region = endpoint_config['signing_region']
141 endpoint_region_name = endpoint_config['region_name']
142 account_id_endpoint_mode = config_kwargs['account_id_endpoint_mode']
144 event_emitter = copy.copy(self._event_emitter)
145 signer = RequestSigner(
146 service_model.service_id,
147 signing_region,
148 endpoint_config['signing_name'],
149 endpoint_config['signature_version'],
150 credentials,
151 event_emitter,
152 auth_token,
153 )
155 config_kwargs['s3'] = s3_config
156 new_config = Config(**config_kwargs)
157 endpoint_creator = EndpointCreator(event_emitter)
159 endpoint = endpoint_creator.create_endpoint(
160 service_model,
161 region_name=endpoint_region_name,
162 endpoint_url=endpoint_config['endpoint_url'],
163 verify=verify,
164 response_parser_factory=self._response_parser_factory,
165 max_pool_connections=new_config.max_pool_connections,
166 proxies=new_config.proxies,
167 timeout=(new_config.connect_timeout, new_config.read_timeout),
168 socket_options=socket_options,
169 client_cert=new_config.client_cert,
170 proxies_config=new_config.proxies_config,
171 )
173 # Emit event to allow service-specific or customer customization of serializer kwargs
174 event_name = f'creating-serializer.{service_name}'
175 serializer_kwargs = {
176 'timestamp_precision': botocore.serialize.TIMESTAMP_PRECISION_DEFAULT
177 }
178 event_emitter.emit(
179 event_name,
180 protocol_name=protocol,
181 service_model=service_model,
182 serializer_kwargs=serializer_kwargs,
183 )
185 serializer = botocore.serialize.create_serializer(
186 protocol,
187 parameter_validation,
188 timestamp_precision=serializer_kwargs['timestamp_precision'],
189 )
190 response_parser = botocore.parsers.create_parser(protocol)
192 ruleset_resolver = self._build_endpoint_resolver(
193 endpoints_ruleset_data,
194 partition_data,
195 client_config,
196 service_model,
197 endpoint_region_name,
198 region_name,
199 configured_endpoint_url,
200 endpoint,
201 is_secure,
202 endpoint_bridge,
203 event_emitter,
204 credentials,
205 account_id_endpoint_mode,
206 )
208 # Copy the session's user agent factory and adds client configuration.
209 client_ua_creator = self._session_ua_creator.with_client_config(
210 new_config
211 )
212 supplied_ua = client_config.user_agent if client_config else None
213 new_config._supplied_user_agent = supplied_ua
215 return {
216 'serializer': serializer,
217 'endpoint': endpoint,
218 'response_parser': response_parser,
219 'event_emitter': event_emitter,
220 'request_signer': signer,
221 'service_model': service_model,
222 'loader': self._loader,
223 'client_config': new_config,
224 'partition': partition,
225 'exceptions_factory': self._exceptions_factory,
226 'endpoint_ruleset_resolver': ruleset_resolver,
227 'user_agent_creator': client_ua_creator,
228 }
230 def compute_client_args(
231 self,
232 service_model,
233 client_config,
234 endpoint_bridge,
235 region_name,
236 endpoint_url,
237 is_secure,
238 scoped_config,
239 ):
240 service_name = service_model.endpoint_prefix
241 protocol = service_model.resolved_protocol
242 parameter_validation = True
243 if client_config and not client_config.parameter_validation:
244 parameter_validation = False
245 elif scoped_config:
246 raw_value = scoped_config.get('parameter_validation')
247 if raw_value is not None:
248 parameter_validation = ensure_boolean(raw_value)
250 s3_config = self.compute_s3_config(client_config)
252 configured_endpoint_url = self._compute_configured_endpoint_url(
253 client_config=client_config,
254 endpoint_url=endpoint_url,
255 )
256 if configured_endpoint_url is not None:
257 register_feature_id('ENDPOINT_OVERRIDE')
259 endpoint_config = self._compute_endpoint_config(
260 service_name=service_name,
261 region_name=region_name,
262 endpoint_url=configured_endpoint_url,
263 is_secure=is_secure,
264 endpoint_bridge=endpoint_bridge,
265 s3_config=s3_config,
266 )
267 endpoint_variant_tags = endpoint_config['metadata'].get('tags', [])
269 # Some third-party libraries expect the final user-agent string in
270 # ``client.meta.config.user_agent``. To maintain backwards
271 # compatibility, the preliminary user-agent string (before any Config
272 # object modifications and without request-specific user-agent
273 # components) is stored in the new Config object's ``user_agent``
274 # property but not used by Botocore itself.
275 preliminary_ua_string = self._session_ua_creator.with_client_config(
276 client_config
277 ).to_string()
278 # Create a new client config to be passed to the client based
279 # on the final values. We do not want the user to be able
280 # to try to modify an existing client with a client config.
281 config_kwargs = dict(
282 region_name=endpoint_config['region_name'],
283 signature_version=endpoint_config['signature_version'],
284 user_agent=preliminary_ua_string,
285 )
286 if 'dualstack' in endpoint_variant_tags:
287 config_kwargs.update(use_dualstack_endpoint=True)
288 if 'fips' in endpoint_variant_tags:
289 config_kwargs.update(use_fips_endpoint=True)
290 if client_config is not None:
291 config_kwargs.update(
292 connect_timeout=client_config.connect_timeout,
293 read_timeout=client_config.read_timeout,
294 max_pool_connections=client_config.max_pool_connections,
295 proxies=client_config.proxies,
296 proxies_config=client_config.proxies_config,
297 retries=client_config.retries,
298 client_cert=client_config.client_cert,
299 inject_host_prefix=client_config.inject_host_prefix,
300 tcp_keepalive=client_config.tcp_keepalive,
301 user_agent_extra=client_config.user_agent_extra,
302 user_agent_appid=client_config.user_agent_appid,
303 request_min_compression_size_bytes=(
304 client_config.request_min_compression_size_bytes
305 ),
306 disable_request_compression=(
307 client_config.disable_request_compression
308 ),
309 client_context_params=client_config.client_context_params,
310 sigv4a_signing_region_set=(
311 client_config.sigv4a_signing_region_set
312 ),
313 request_checksum_calculation=(
314 client_config.request_checksum_calculation
315 ),
316 response_checksum_validation=(
317 client_config.response_checksum_validation
318 ),
319 account_id_endpoint_mode=client_config.account_id_endpoint_mode,
320 auth_scheme_preference=client_config.auth_scheme_preference,
321 )
322 self._compute_retry_config(config_kwargs)
323 self._compute_connect_timeout(config_kwargs)
324 self._compute_user_agent_appid_config(config_kwargs)
325 self._compute_request_compression_config(config_kwargs)
326 self._compute_sigv4a_signing_region_set_config(config_kwargs)
327 self._compute_checksum_config(config_kwargs)
328 self._compute_account_id_endpoint_mode_config(config_kwargs)
329 self._compute_inject_host_prefix(client_config, config_kwargs)
330 self._compute_auth_scheme_preference_config(
331 client_config, config_kwargs
332 )
333 self._compute_signature_version_config(client_config, config_kwargs)
334 s3_config = self.compute_s3_config(client_config)
336 is_s3_service = self._is_s3_service(service_name)
338 if is_s3_service and 'dualstack' in endpoint_variant_tags:
339 if s3_config is None:
340 s3_config = {}
341 s3_config['use_dualstack_endpoint'] = True
343 return {
344 'service_name': service_name,
345 'parameter_validation': parameter_validation,
346 'configured_endpoint_url': configured_endpoint_url,
347 'endpoint_config': endpoint_config,
348 'protocol': protocol,
349 'config_kwargs': config_kwargs,
350 's3_config': s3_config,
351 'socket_options': self._compute_socket_options(
352 scoped_config, client_config
353 ),
354 }
356 def _compute_inject_host_prefix(self, client_config, config_kwargs):
357 # In the cases that a Config object was not provided, or the private value
358 # remained UNSET, we should resolve the value from the config store.
359 if (
360 client_config is None
361 or client_config._inject_host_prefix == 'UNSET'
362 ):
363 configured_disable_host_prefix_injection = (
364 self._config_store.get_config_variable(
365 'disable_host_prefix_injection'
366 )
367 )
368 if configured_disable_host_prefix_injection is not None:
369 config_kwargs[
370 'inject_host_prefix'
371 ] = not configured_disable_host_prefix_injection
372 else:
373 config_kwargs['inject_host_prefix'] = True
375 def _compute_configured_endpoint_url(self, client_config, endpoint_url):
376 if endpoint_url is not None:
377 return endpoint_url
379 if self._ignore_configured_endpoint_urls(client_config):
380 logger.debug("Ignoring configured endpoint URLs.")
381 return endpoint_url
383 return self._config_store.get_config_variable('endpoint_url')
385 def _ignore_configured_endpoint_urls(self, client_config):
386 if (
387 client_config
388 and client_config.ignore_configured_endpoint_urls is not None
389 ):
390 return client_config.ignore_configured_endpoint_urls
392 return self._config_store.get_config_variable(
393 'ignore_configured_endpoint_urls'
394 )
396 def compute_s3_config(self, client_config):
397 s3_configuration = self._config_store.get_config_variable('s3')
399 # Next specific client config values takes precedence over
400 # specific values in the scoped config.
401 if client_config is not None:
402 if client_config.s3 is not None:
403 if s3_configuration is None:
404 s3_configuration = client_config.s3
405 else:
406 # The current s3_configuration dictionary may be
407 # from a source that only should be read from so
408 # we want to be safe and just make a copy of it to modify
409 # before it actually gets updated.
410 s3_configuration = s3_configuration.copy()
411 s3_configuration.update(client_config.s3)
413 return s3_configuration
415 def _is_s3_service(self, service_name):
416 """Whether the service is S3 or S3 Control.
418 Note that throughout this class, service_name refers to the endpoint
419 prefix, not the folder name of the service in botocore/data. For
420 S3 Control, the folder name is 's3control' but the endpoint prefix is
421 's3-control'.
422 """
423 return service_name in ['s3', 's3-control']
425 def _compute_endpoint_config(
426 self,
427 service_name,
428 region_name,
429 endpoint_url,
430 is_secure,
431 endpoint_bridge,
432 s3_config,
433 ):
434 resolve_endpoint_kwargs = {
435 'service_name': service_name,
436 'region_name': region_name,
437 'endpoint_url': endpoint_url,
438 'is_secure': is_secure,
439 'endpoint_bridge': endpoint_bridge,
440 }
441 if service_name == 's3':
442 return self._compute_s3_endpoint_config(
443 s3_config=s3_config, **resolve_endpoint_kwargs
444 )
445 if service_name == 'sts':
446 return self._compute_sts_endpoint_config(**resolve_endpoint_kwargs)
447 return self._resolve_endpoint(**resolve_endpoint_kwargs)
449 def _compute_s3_endpoint_config(
450 self, s3_config, **resolve_endpoint_kwargs
451 ):
452 force_s3_global = self._should_force_s3_global(
453 resolve_endpoint_kwargs['region_name'], s3_config
454 )
455 if force_s3_global:
456 resolve_endpoint_kwargs['region_name'] = None
457 endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
458 self._set_region_if_custom_s3_endpoint(
459 endpoint_config, resolve_endpoint_kwargs['endpoint_bridge']
460 )
461 # For backwards compatibility reasons, we want to make sure the
462 # client.meta.region_name will remain us-east-1 if we forced the
463 # endpoint to be the global region. Specifically, if this value
464 # changes to aws-global, it breaks logic where a user is checking
465 # for us-east-1 as the global endpoint such as in creating buckets.
466 if force_s3_global and endpoint_config['region_name'] == 'aws-global':
467 endpoint_config['region_name'] = 'us-east-1'
468 return endpoint_config
470 def _should_force_s3_global(self, region_name, s3_config):
471 s3_regional_config = 'legacy'
472 if s3_config and 'us_east_1_regional_endpoint' in s3_config:
473 s3_regional_config = s3_config['us_east_1_regional_endpoint']
474 self._validate_s3_regional_config(s3_regional_config)
476 is_global_region = region_name in ('us-east-1', None)
477 return s3_regional_config == 'legacy' and is_global_region
479 def _validate_s3_regional_config(self, config_val):
480 if config_val not in VALID_REGIONAL_ENDPOINTS_CONFIG:
481 raise botocore.exceptions.InvalidS3UsEast1RegionalEndpointConfigError(
482 s3_us_east_1_regional_endpoint_config=config_val
483 )
485 def _set_region_if_custom_s3_endpoint(
486 self, endpoint_config, endpoint_bridge
487 ):
488 # If a user is providing a custom URL, the endpoint resolver will
489 # refuse to infer a signing region. If we want to default to s3v4,
490 # we have to account for this.
491 if (
492 endpoint_config['signing_region'] is None
493 and endpoint_config['region_name'] is None
494 ):
495 endpoint = endpoint_bridge.resolve('s3')
496 endpoint_config['signing_region'] = endpoint['signing_region']
497 endpoint_config['region_name'] = endpoint['region_name']
499 def _compute_sts_endpoint_config(self, **resolve_endpoint_kwargs):
500 endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
501 if self._should_set_global_sts_endpoint(
502 resolve_endpoint_kwargs['region_name'],
503 resolve_endpoint_kwargs['endpoint_url'],
504 endpoint_config,
505 ):
506 self._set_global_sts_endpoint(
507 endpoint_config, resolve_endpoint_kwargs['is_secure']
508 )
509 return endpoint_config
511 def _should_set_global_sts_endpoint(
512 self, region_name, endpoint_url, endpoint_config
513 ):
514 has_variant_tags = endpoint_config and endpoint_config.get(
515 'metadata', {}
516 ).get('tags')
517 if endpoint_url or has_variant_tags:
518 return False
519 return (
520 self._get_sts_regional_endpoints_config() == 'legacy'
521 and region_name in LEGACY_GLOBAL_STS_REGIONS
522 )
524 def _get_sts_regional_endpoints_config(self):
525 sts_regional_endpoints_config = self._config_store.get_config_variable(
526 'sts_regional_endpoints'
527 )
528 if not sts_regional_endpoints_config:
529 sts_regional_endpoints_config = 'regional'
530 if (
531 sts_regional_endpoints_config
532 not in VALID_REGIONAL_ENDPOINTS_CONFIG
533 ):
534 raise botocore.exceptions.InvalidSTSRegionalEndpointsConfigError(
535 sts_regional_endpoints_config=sts_regional_endpoints_config
536 )
537 return sts_regional_endpoints_config
539 def _set_global_sts_endpoint(self, endpoint_config, is_secure):
540 scheme = 'https' if is_secure else 'http'
541 endpoint_config['endpoint_url'] = f'{scheme}://sts.amazonaws.com'
542 endpoint_config['signing_region'] = 'us-east-1'
544 def _resolve_endpoint(
545 self,
546 service_name,
547 region_name,
548 endpoint_url,
549 is_secure,
550 endpoint_bridge,
551 ):
552 return endpoint_bridge.resolve(
553 service_name, region_name, endpoint_url, is_secure
554 )
556 def _compute_socket_options(self, scoped_config, client_config=None):
557 # This disables Nagle's algorithm and is the default socket options
558 # in urllib3.
560 socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
561 client_keepalive = client_config and client_config.tcp_keepalive
562 if client_keepalive is None:
563 client_keepalive = self._config_store.get_config_variable(
564 'tcp_keepalive'
565 )
567 if client_keepalive:
568 socket_options.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
569 return socket_options
571 def _compute_retry_config(self, config_kwargs):
572 self._compute_retry_max_attempts(config_kwargs)
573 self._compute_retry_mode(config_kwargs)
575 def _compute_retry_max_attempts(self, config_kwargs):
576 # There's a pre-existing max_attempts client config value that actually
577 # means max *retry* attempts. There's also a `max_attempts` we pull
578 # from the config store that means *total attempts*, which includes the
579 # intitial request. We can't change what `max_attempts` means in
580 # client config so we try to normalize everything to a new
581 # "total_max_attempts" variable. We ensure that after this, the only
582 # configuration for "max attempts" is the 'total_max_attempts' key.
583 # An explicitly provided max_attempts in the client config
584 # overrides everything.
585 retries = config_kwargs.get('retries')
586 if retries is not None:
587 if 'total_max_attempts' in retries:
588 retries.pop('max_attempts', None)
589 return
590 if 'max_attempts' in retries:
591 value = retries.pop('max_attempts')
592 # client config max_attempts means total retries so we
593 # have to add one for 'total_max_attempts' to account
594 # for the initial request.
595 retries['total_max_attempts'] = value + 1
596 return
597 # Otherwise we'll check the config store which checks env vars,
598 # config files, etc. There is no default value for max_attempts
599 # so if this returns None and we don't set a default value here.
600 max_attempts = self._config_store.get_config_variable('max_attempts')
601 if max_attempts is not None:
602 if retries is None:
603 retries = {}
604 config_kwargs['retries'] = retries
605 retries['total_max_attempts'] = max_attempts
607 def _compute_retry_mode(self, config_kwargs):
608 retries = config_kwargs.get('retries')
609 if retries is None:
610 retries = {}
611 config_kwargs['retries'] = retries
612 elif 'mode' in retries:
613 # If there's a retry mode explicitly set in the client config
614 # that overrides everything.
615 return
616 retry_mode = self._config_store.get_config_variable('retry_mode')
617 if retry_mode is None:
618 retry_mode = 'legacy'
619 retries['mode'] = retry_mode
621 def _compute_connect_timeout(self, config_kwargs):
622 # Checking if connect_timeout is set on the client config.
623 # If it is not, we check the config_store in case a
624 # non legacy default mode has been configured.
625 connect_timeout = config_kwargs.get('connect_timeout')
626 if connect_timeout is not None:
627 return
628 connect_timeout = self._config_store.get_config_variable(
629 'connect_timeout'
630 )
631 if connect_timeout:
632 config_kwargs['connect_timeout'] = connect_timeout
634 def _compute_request_compression_config(self, config_kwargs):
635 min_size = config_kwargs.get('request_min_compression_size_bytes')
636 disabled = config_kwargs.get('disable_request_compression')
637 if min_size is None:
638 min_size = self._config_store.get_config_variable(
639 'request_min_compression_size_bytes'
640 )
641 # conversion func is skipped so input validation must be done here
642 # regardless if the value is coming from the config store or the
643 # config object
644 min_size = self._validate_min_compression_size(min_size)
645 config_kwargs['request_min_compression_size_bytes'] = min_size
647 if disabled is None:
648 disabled = self._config_store.get_config_variable(
649 'disable_request_compression'
650 )
651 else:
652 # if the user provided a value we must check if it's a boolean
653 disabled = ensure_boolean(disabled)
654 config_kwargs['disable_request_compression'] = disabled
656 def _validate_min_compression_size(self, min_size):
657 min_allowed_min_size = 1
658 max_allowed_min_size = 1048576
659 error_msg_base = (
660 f'Invalid value "{min_size}" for '
661 'request_min_compression_size_bytes.'
662 )
663 try:
664 min_size = int(min_size)
665 except (ValueError, TypeError):
666 msg = (
667 f'{error_msg_base} Value must be an integer. '
668 f'Received {type(min_size)} instead.'
669 )
670 raise botocore.exceptions.InvalidConfigError(error_msg=msg)
671 if not min_allowed_min_size <= min_size <= max_allowed_min_size:
672 msg = (
673 f'{error_msg_base} Value must be between '
674 f'{min_allowed_min_size} and {max_allowed_min_size}.'
675 )
676 raise botocore.exceptions.InvalidConfigError(error_msg=msg)
678 return min_size
680 def _ensure_boolean(self, val):
681 if isinstance(val, bool):
682 return val
683 else:
684 return val.lower() == 'true'
686 def _build_endpoint_resolver(
687 self,
688 endpoints_ruleset_data,
689 partition_data,
690 client_config,
691 service_model,
692 endpoint_region_name,
693 region_name,
694 endpoint_url,
695 endpoint,
696 is_secure,
697 endpoint_bridge,
698 event_emitter,
699 credentials,
700 account_id_endpoint_mode,
701 ):
702 if endpoints_ruleset_data is None:
703 return None
705 # The legacy EndpointResolver is global to the session, but
706 # EndpointRulesetResolver is service-specific. Builtins for
707 # EndpointRulesetResolver must not be derived from the legacy
708 # endpoint resolver's output, including final_args, s3_config,
709 # etc.
710 s3_config_raw = self.compute_s3_config(client_config) or {}
711 service_name_raw = service_model.endpoint_prefix
712 # Maintain complex logic for s3 and sts endpoints for backwards
713 # compatibility.
714 if service_name_raw in ['s3', 'sts'] or region_name is None:
715 eprv2_region_name = endpoint_region_name
716 else:
717 eprv2_region_name = region_name
718 resolver_builtins = self.compute_endpoint_resolver_builtin_defaults(
719 region_name=eprv2_region_name,
720 service_name=service_name_raw,
721 s3_config=s3_config_raw,
722 endpoint_bridge=endpoint_bridge,
723 client_endpoint_url=endpoint_url,
724 legacy_endpoint_url=endpoint.host,
725 credentials=credentials,
726 account_id_endpoint_mode=account_id_endpoint_mode,
727 )
728 # Client context params for s3 conflict with the available settings
729 # in the `s3` parameter on the `Config` object. If the same parameter
730 # is set in both places, the value in the `s3` parameter takes priority.
731 if client_config is not None:
732 client_context = client_config.client_context_params or {}
733 else:
734 client_context = {}
735 if self._is_s3_service(service_name_raw):
736 client_context.update(s3_config_raw)
738 sig_version = (
739 client_config.signature_version
740 if client_config is not None
741 else None
742 )
743 return EndpointRulesetResolver(
744 endpoint_ruleset_data=endpoints_ruleset_data,
745 partition_data=partition_data,
746 service_model=service_model,
747 builtins=resolver_builtins,
748 client_context=client_context,
749 event_emitter=event_emitter,
750 use_ssl=is_secure,
751 requested_auth_scheme=sig_version,
752 )
754 def compute_endpoint_resolver_builtin_defaults(
755 self,
756 region_name,
757 service_name,
758 s3_config,
759 endpoint_bridge,
760 client_endpoint_url,
761 legacy_endpoint_url,
762 credentials,
763 account_id_endpoint_mode,
764 ):
765 # EndpointRulesetResolver rulesets may accept an "SDK::Endpoint" as
766 # input. If the endpoint_url argument of create_client() is set, it
767 # always takes priority.
768 if client_endpoint_url:
769 given_endpoint = client_endpoint_url
770 # If an endpoints.json data file other than the one bundled within
771 # the botocore/data directory is used, the output of legacy
772 # endpoint resolution is provided to EndpointRulesetResolver.
773 elif not endpoint_bridge.resolver_uses_builtin_data():
774 given_endpoint = legacy_endpoint_url
775 else:
776 given_endpoint = None
778 # The endpoint rulesets differ from legacy botocore behavior in whether
779 # forcing path style addressing in incompatible situations raises an
780 # exception or silently ignores the config setting. The
781 # AWS_S3_FORCE_PATH_STYLE parameter is adjusted both here and for each
782 # operation so that the ruleset behavior is backwards compatible.
783 if s3_config.get('use_accelerate_endpoint', False):
784 force_path_style = False
785 elif client_endpoint_url is not None and not is_s3_accelerate_url(
786 client_endpoint_url
787 ):
788 force_path_style = s3_config.get('addressing_style') != 'virtual'
789 else:
790 force_path_style = s3_config.get('addressing_style') == 'path'
792 return {
793 EPRBuiltins.AWS_REGION: region_name,
794 EPRBuiltins.AWS_USE_FIPS: (
795 # SDK_ENDPOINT cannot be combined with AWS_USE_FIPS
796 given_endpoint is None
797 # use legacy resolver's _resolve_endpoint_variant_config_var()
798 # or default to False if it returns None
799 and endpoint_bridge._resolve_endpoint_variant_config_var(
800 'use_fips_endpoint'
801 )
802 or False
803 ),
804 EPRBuiltins.AWS_USE_DUALSTACK: (
805 # SDK_ENDPOINT cannot be combined with AWS_USE_DUALSTACK
806 given_endpoint is None
807 # use legacy resolver's _resolve_use_dualstack_endpoint() and
808 # or default to False if it returns None
809 and endpoint_bridge._resolve_use_dualstack_endpoint(
810 service_name
811 )
812 or False
813 ),
814 EPRBuiltins.AWS_STS_USE_GLOBAL_ENDPOINT: (
815 self._should_set_global_sts_endpoint(
816 region_name=region_name,
817 endpoint_url=None,
818 endpoint_config=None,
819 )
820 ),
821 EPRBuiltins.AWS_S3_USE_GLOBAL_ENDPOINT: (
822 self._should_force_s3_global(region_name, s3_config)
823 ),
824 EPRBuiltins.AWS_S3_ACCELERATE: s3_config.get(
825 'use_accelerate_endpoint', False
826 ),
827 EPRBuiltins.AWS_S3_FORCE_PATH_STYLE: force_path_style,
828 EPRBuiltins.AWS_S3_USE_ARN_REGION: s3_config.get(
829 'use_arn_region', True
830 ),
831 EPRBuiltins.AWS_S3CONTROL_USE_ARN_REGION: s3_config.get(
832 'use_arn_region', False
833 ),
834 EPRBuiltins.AWS_S3_DISABLE_MRAP: s3_config.get(
835 's3_disable_multiregion_access_points', False
836 ),
837 EPRBuiltins.SDK_ENDPOINT: given_endpoint,
838 EPRBuiltins.ACCOUNT_ID: credentials.get_deferred_property(
839 'account_id'
840 )
841 if credentials
842 else None,
843 EPRBuiltins.ACCOUNT_ID_ENDPOINT_MODE: account_id_endpoint_mode,
844 }
846 def _compute_user_agent_appid_config(self, config_kwargs):
847 user_agent_appid = config_kwargs.get('user_agent_appid')
848 if user_agent_appid is None:
849 user_agent_appid = self._config_store.get_config_variable(
850 'user_agent_appid'
851 )
852 if (
853 user_agent_appid is not None
854 and len(user_agent_appid) > USERAGENT_APPID_MAXLEN
855 ):
856 logger.warning(
857 'The configured value for user_agent_appid exceeds the '
858 'maximum length of %d characters.',
859 USERAGENT_APPID_MAXLEN,
860 )
861 config_kwargs['user_agent_appid'] = user_agent_appid
863 def _compute_sigv4a_signing_region_set_config(self, config_kwargs):
864 sigv4a_signing_region_set = config_kwargs.get(
865 'sigv4a_signing_region_set'
866 )
867 if sigv4a_signing_region_set is None:
868 sigv4a_signing_region_set = self._config_store.get_config_variable(
869 'sigv4a_signing_region_set'
870 )
871 config_kwargs['sigv4a_signing_region_set'] = sigv4a_signing_region_set
873 def _compute_checksum_config(self, config_kwargs):
874 self._handle_checksum_config(
875 config_kwargs,
876 config_key="request_checksum_calculation",
877 valid_options=VALID_REQUEST_CHECKSUM_CALCULATION_CONFIG,
878 )
879 self._handle_checksum_config(
880 config_kwargs,
881 config_key="response_checksum_validation",
882 valid_options=VALID_RESPONSE_CHECKSUM_VALIDATION_CONFIG,
883 )
885 def _handle_checksum_config(
886 self,
887 config_kwargs,
888 config_key,
889 valid_options,
890 ):
891 value = config_kwargs.get(config_key)
892 if value is None:
893 value = self._config_store.get_config_variable(config_key)
895 if isinstance(value, str):
896 value = value.lower()
898 if value not in valid_options:
899 raise botocore.exceptions.InvalidChecksumConfigError(
900 config_key=config_key,
901 config_value=value,
902 valid_options=valid_options,
903 )
904 self._register_checksum_config_feature_ids(value, config_key)
905 config_kwargs[config_key] = value
907 def _register_checksum_config_feature_ids(self, value, config_key):
908 checksum_config_feature_id = None
909 if config_key == "request_checksum_calculation":
910 checksum_config_feature_id = (
911 f"FLEXIBLE_CHECKSUMS_REQ_{value.upper()}"
912 )
913 elif config_key == "response_checksum_validation":
914 checksum_config_feature_id = (
915 f"FLEXIBLE_CHECKSUMS_RES_{value.upper()}"
916 )
917 if checksum_config_feature_id is not None:
918 register_feature_id(checksum_config_feature_id)
920 def _compute_account_id_endpoint_mode_config(self, config_kwargs):
921 config_key = 'account_id_endpoint_mode'
923 # Disable account id based endpoint routing for unsigned requests
924 # since there are no credentials to resolve.
925 signature_version = config_kwargs.get('signature_version')
926 if signature_version is botocore.UNSIGNED:
927 config_kwargs[config_key] = 'disabled'
928 return
930 account_id_endpoint_mode = config_kwargs.get(config_key)
931 if account_id_endpoint_mode is None:
932 account_id_endpoint_mode = self._config_store.get_config_variable(
933 config_key
934 )
936 if isinstance(account_id_endpoint_mode, str):
937 account_id_endpoint_mode = account_id_endpoint_mode.lower()
939 if (
940 account_id_endpoint_mode
941 not in VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG
942 ):
943 raise botocore.exceptions.InvalidConfigError(
944 error_msg=f"The configured value '{account_id_endpoint_mode}' for '{config_key}' is "
945 f"invalid. Valid values are: {VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG}."
946 )
948 config_kwargs[config_key] = account_id_endpoint_mode
950 def _compute_auth_scheme_preference_config(
951 self, client_config, config_kwargs
952 ):
953 config_key = 'auth_scheme_preference'
954 set_in_config_object = False
956 if client_config and client_config.auth_scheme_preference:
957 value = client_config.auth_scheme_preference
958 set_in_config_object = True
959 else:
960 value = self._config_store.get_config_variable(config_key)
962 if value is None:
963 config_kwargs[config_key] = None
964 return
966 if not isinstance(value, str):
967 raise botocore.exceptions.InvalidConfigError(
968 error_msg=(
969 f"{config_key} must be a comma-delimited string. "
970 f"Received {type(value)} instead: {value}."
971 )
972 )
974 value = ','.join(
975 item.replace(' ', '').replace('\t', '')
976 for item in value.split(',')
977 if item.strip()
978 )
980 if set_in_config_object:
981 value = ClientConfigString(value)
983 config_kwargs[config_key] = value
985 def _compute_signature_version_config(self, client_config, config_kwargs):
986 if client_config and client_config.signature_version:
987 value = client_config.signature_version
988 if isinstance(value, str):
989 config_kwargs['signature_version'] = ClientConfigString(value)
992class ConfigObjectWrapper:
993 """Base class to mark values set via in-code Config object."""
995 pass
998class ClientConfigString(str, ConfigObjectWrapper):
999 def __new__(cls, value=None):
1000 return super().__new__(cls, value)