aws_config/lib.rs
1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/* Automatically managed default lints */
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8/* End of automatically managed default lints */
9#![allow(clippy::derive_partial_eq_without_eq)]
10#![warn(
11 missing_debug_implementations,
12 missing_docs,
13 rust_2018_idioms,
14 rustdoc::missing_crate_level_docs,
15 unreachable_pub
16)]
17// Allow disallowed methods in tests
18#![cfg_attr(test, allow(clippy::disallowed_methods))]
19
20//! `aws-config` provides implementations of region and credential resolution.
21//!
22//! These implementations can be used either via the default chain implementation
23//! [`from_env`]/[`ConfigLoader`] or ad-hoc individual credential and region providers.
24//!
25//! [`ConfigLoader`] can combine different configuration sources into an AWS shared-config:
26//! [`SdkConfig`]. `SdkConfig` can be used configure an AWS service client.
27//!
28//! # Examples
29//!
30//! Load default SDK configuration:
31//! ```no_run
32//! use aws_config::BehaviorVersion;
33//! mod aws_sdk_dynamodb {
34//! # pub struct Client;
35//! # impl Client {
36//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
37//! # }
38//! # }
39//! # async fn docs() {
40//! let config = aws_config::load_defaults(BehaviorVersion::v2023_11_09()).await;
41//! let client = aws_sdk_dynamodb::Client::new(&config);
42//! # }
43//! ```
44//!
45//! Load SDK configuration with a region override:
46//! ```no_run
47//! # mod aws_sdk_dynamodb {
48//! # pub struct Client;
49//! # impl Client {
50//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
51//! # }
52//! # }
53//! # async fn docs() {
54//! # use aws_config::meta::region::RegionProviderChain;
55//! let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
56//! // Note: requires the `behavior-version-latest` feature enabled
57//! let config = aws_config::from_env().region(region_provider).load().await;
58//! let client = aws_sdk_dynamodb::Client::new(&config);
59//! # }
60//! ```
61//!
62//! Override configuration after construction of `SdkConfig`:
63//!
64//! ```no_run
65//! # use aws_credential_types::provider::ProvideCredentials;
66//! # use aws_types::SdkConfig;
67//! # mod aws_sdk_dynamodb {
68//! # pub mod config {
69//! # pub struct Builder;
70//! # impl Builder {
71//! # pub fn credentials_provider(
72//! # self,
73//! # credentials_provider: impl aws_credential_types::provider::ProvideCredentials + 'static) -> Self { self }
74//! # pub fn build(self) -> Builder { self }
75//! # }
76//! # impl From<&aws_types::SdkConfig> for Builder {
77//! # fn from(_: &aws_types::SdkConfig) -> Self {
78//! # todo!()
79//! # }
80//! # }
81//! # }
82//! # pub struct Client;
83//! # impl Client {
84//! # pub fn from_conf(conf: config::Builder) -> Self { Client }
85//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
86//! # }
87//! # }
88//! # async fn docs() {
89//! # use aws_config::meta::region::RegionProviderChain;
90//! # fn custom_provider(base: &SdkConfig) -> impl ProvideCredentials {
91//! # base.credentials_provider().unwrap().clone()
92//! # }
93//! let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
94//! let custom_credentials_provider = custom_provider(&sdk_config);
95//! let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
96//! .credentials_provider(custom_credentials_provider)
97//! .build();
98//! let client = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
99//! # }
100//! ```
101
102pub use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
103// Re-export types from aws-types
104pub use aws_types::{
105 app_name::{AppName, InvalidAppName},
106 region::Region,
107 SdkConfig,
108};
109/// Load default sources for all configuration with override support
110pub use loader::ConfigLoader;
111
112/// Types for configuring identity caching.
113pub mod identity {
114 pub use aws_smithy_runtime::client::identity::IdentityCache;
115 pub use aws_smithy_runtime::client::identity::LazyCacheBuilder;
116}
117
118#[allow(dead_code)]
119const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
120
121mod http_credential_provider;
122mod json_credentials;
123#[cfg(test)]
124mod test_case;
125
126pub mod credential_process;
127pub mod default_provider;
128pub mod ecs;
129mod env_service_config;
130pub mod environment;
131pub mod imds;
132pub mod meta;
133pub mod profile;
134pub mod provider_config;
135pub mod retry;
136mod sensitive_command;
137#[cfg(feature = "sso")]
138pub mod sso;
139pub mod stalled_stream_protection;
140pub mod sts;
141pub mod timeout;
142pub mod web_identity_token;
143
144/// Create a config loader with the _latest_ defaults.
145///
146/// This loader will always set [`BehaviorVersion::latest`].
147///
148/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
149///
150/// # Examples
151/// ```no_run
152/// # async fn create_config() {
153/// let config = aws_config::from_env().region("us-east-1").load().await;
154/// # }
155/// ```
156#[cfg(feature = "behavior-version-latest")]
157pub fn from_env() -> ConfigLoader {
158 ConfigLoader::default().behavior_version(BehaviorVersion::latest())
159}
160
161/// Load default configuration with the _latest_ defaults.
162///
163/// Convenience wrapper equivalent to `aws_config::load_defaults(BehaviorVersion::latest()).await`
164///
165/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
166#[cfg(feature = "behavior-version-latest")]
167pub async fn load_from_env() -> SdkConfig {
168 from_env().load().await
169}
170
171/// Create a config loader with the _latest_ defaults.
172#[cfg(not(feature = "behavior-version-latest"))]
173#[deprecated(
174 note = "Use the `aws_config::defaults` function. If you don't care about future default behavior changes, you can continue to use this function by enabling the `behavior-version-latest` feature. Doing so will make this deprecation notice go away."
175)]
176pub fn from_env() -> ConfigLoader {
177 ConfigLoader::default().behavior_version(BehaviorVersion::latest())
178}
179
180/// Load default configuration with the _latest_ defaults.
181#[cfg(not(feature = "behavior-version-latest"))]
182#[deprecated(
183 note = "Use the `aws_config::load_defaults` function. If you don't care about future default behavior changes, you can continue to use this function by enabling the `behavior-version-latest` feature. Doing so will make this deprecation notice go away."
184)]
185pub async fn load_from_env() -> SdkConfig {
186 load_defaults(BehaviorVersion::latest()).await
187}
188
189/// Create a config loader with the defaults for the given behavior version.
190///
191/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
192///
193/// # Examples
194/// ```no_run
195/// # async fn create_config() {
196/// use aws_config::BehaviorVersion;
197/// let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
198/// .region("us-east-1")
199/// .load()
200/// .await;
201/// # }
202/// ```
203pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
204 ConfigLoader::default().behavior_version(version)
205}
206
207/// Load default configuration with the given behavior version.
208///
209/// Convenience wrapper equivalent to `aws_config::defaults(behavior_version).load().await`
210///
211/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
212pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
213 defaults(version).load().await
214}
215
216mod loader {
217 use crate::env_service_config::EnvServiceConfig;
218 use aws_credential_types::provider::{
219 token::{ProvideToken, SharedTokenProvider},
220 ProvideCredentials, SharedCredentialsProvider,
221 };
222 use aws_credential_types::Credentials;
223 use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
224 use aws_smithy_async::time::{SharedTimeSource, TimeSource};
225 use aws_smithy_runtime::client::identity::IdentityCache;
226 use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
227 use aws_smithy_runtime_api::client::http::HttpClient;
228 use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
229 use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
230 use aws_smithy_runtime_api::shared::IntoShared;
231 use aws_smithy_types::checksum_config::{
232 RequestChecksumCalculation, ResponseChecksumValidation,
233 };
234 use aws_smithy_types::retry::RetryConfig;
235 use aws_smithy_types::timeout::TimeoutConfig;
236 use aws_types::app_name::AppName;
237 use aws_types::docs_for;
238 use aws_types::endpoint_config::AccountIdEndpointMode;
239 use aws_types::origin::Origin;
240 use aws_types::os_shim_internal::{Env, Fs};
241 use aws_types::sdk_config::SharedHttpClient;
242 use aws_types::SdkConfig;
243
244 use crate::default_provider::{
245 account_id_endpoint_mode, app_name, checksums, credentials, disable_request_compression,
246 endpoint_url, ignore_configured_endpoint_urls as ignore_ep, region,
247 request_min_compression_size_bytes, retry_config, timeout_config, use_dual_stack, use_fips,
248 };
249 use crate::meta::region::ProvideRegion;
250 #[allow(deprecated)]
251 use crate::profile::profile_file::ProfileFiles;
252 use crate::provider_config::ProviderConfig;
253
254 #[derive(Default, Debug)]
255 enum TriStateOption<T> {
256 /// No option was set by the user. We can set up the default.
257 #[default]
258 NotSet,
259 /// The option was explicitly unset. Do not set up a default.
260 ExplicitlyUnset,
261 /// Use the given user provided option.
262 Set(T),
263 }
264
265 /// Load a cross-service [`SdkConfig`] from the environment
266 ///
267 /// This builder supports overriding individual components of the generated config. Overriding a component
268 /// will skip the standard resolution chain from **for that component**. For example,
269 /// if you override the region provider, _even if that provider returns None_, the default region provider
270 /// chain will not be used.
271 #[derive(Default, Debug)]
272 pub struct ConfigLoader {
273 app_name: Option<AppName>,
274 identity_cache: Option<SharedIdentityCache>,
275 credentials_provider: TriStateOption<SharedCredentialsProvider>,
276 token_provider: Option<SharedTokenProvider>,
277 account_id_endpoint_mode: Option<AccountIdEndpointMode>,
278 endpoint_url: Option<String>,
279 region: Option<Box<dyn ProvideRegion>>,
280 retry_config: Option<RetryConfig>,
281 sleep: Option<SharedAsyncSleep>,
282 timeout_config: Option<TimeoutConfig>,
283 provider_config: Option<ProviderConfig>,
284 http_client: Option<SharedHttpClient>,
285 profile_name_override: Option<String>,
286 #[allow(deprecated)]
287 profile_files_override: Option<ProfileFiles>,
288 use_fips: Option<bool>,
289 use_dual_stack: Option<bool>,
290 time_source: Option<SharedTimeSource>,
291 disable_request_compression: Option<bool>,
292 request_min_compression_size_bytes: Option<u32>,
293 stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
294 env: Option<Env>,
295 fs: Option<Fs>,
296 behavior_version: Option<BehaviorVersion>,
297 request_checksum_calculation: Option<RequestChecksumCalculation>,
298 response_checksum_validation: Option<ResponseChecksumValidation>,
299 }
300
301 impl ConfigLoader {
302 /// Sets the [`BehaviorVersion`] used to build [`SdkConfig`].
303 pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
304 self.behavior_version = Some(behavior_version);
305 self
306 }
307
308 /// Override the region used to build [`SdkConfig`].
309 ///
310 /// # Examples
311 /// ```no_run
312 /// # async fn create_config() {
313 /// use aws_types::region::Region;
314 /// let config = aws_config::from_env()
315 /// .region(Region::new("us-east-1"))
316 /// .load().await;
317 /// # }
318 /// ```
319 pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
320 self.region = Some(Box::new(region));
321 self
322 }
323
324 /// Override the retry_config used to build [`SdkConfig`].
325 ///
326 /// # Examples
327 /// ```no_run
328 /// # async fn create_config() {
329 /// use aws_config::retry::RetryConfig;
330 ///
331 /// let config = aws_config::from_env()
332 /// .retry_config(RetryConfig::standard().with_max_attempts(2))
333 /// .load()
334 /// .await;
335 /// # }
336 /// ```
337 pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
338 self.retry_config = Some(retry_config);
339 self
340 }
341
342 /// Override the timeout config used to build [`SdkConfig`].
343 ///
344 /// This will be merged with timeouts coming from the timeout information provider, which
345 /// currently includes a default `CONNECT` timeout of `3.1s`.
346 ///
347 /// If you want to disable timeouts, use [`TimeoutConfig::disabled`]. If you want to disable
348 /// a specific timeout, use `TimeoutConfig::set_<type>(None)`.
349 ///
350 /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials
351 /// provider chain are configured separately.
352 ///
353 /// # Examples
354 /// ```no_run
355 /// # use std::time::Duration;
356 /// # async fn create_config() {
357 /// use aws_config::timeout::TimeoutConfig;
358 ///
359 /// let config = aws_config::from_env()
360 /// .timeout_config(
361 /// TimeoutConfig::builder()
362 /// .operation_timeout(Duration::from_secs(5))
363 /// .build()
364 /// )
365 /// .load()
366 /// .await;
367 /// # }
368 /// ```
369 pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
370 self.timeout_config = Some(timeout_config);
371 self
372 }
373
374 /// Override the sleep implementation for this [`ConfigLoader`].
375 ///
376 /// The sleep implementation is used to create timeout futures.
377 /// You generally won't need to change this unless you're using an async runtime other
378 /// than Tokio.
379 pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
380 // it's possible that we could wrapping an `Arc in an `Arc` and that's OK
381 self.sleep = Some(sleep.into_shared());
382 self
383 }
384
385 /// Set the time source used for tasks like signing requests.
386 ///
387 /// You generally won't need to change this unless you're compiling for a target
388 /// that can't provide a default, such as WASM, or unless you're writing a test against
389 /// the client that needs a fixed time.
390 pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
391 self.time_source = Some(time_source.into_shared());
392 self
393 }
394
395 /// Override the [`HttpClient`] for this [`ConfigLoader`].
396 ///
397 /// The HTTP client will be used for both AWS services and credentials providers.
398 ///
399 /// If you wish to use a separate HTTP client for credentials providers when creating clients,
400 /// then override the HTTP client set with this function on the client-specific `Config`s.
401 pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
402 self.http_client = Some(http_client.into_shared());
403 self
404 }
405
406 /// Override the identity cache used to build [`SdkConfig`].
407 ///
408 /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
409 /// that will load credentials upon first request, cache them, and then reload them during
410 /// another request when they are close to expiring.
411 ///
412 /// # Examples
413 ///
414 /// Change a setting on the default lazy caching implementation:
415 /// ```no_run
416 /// use aws_config::identity::IdentityCache;
417 /// use std::time::Duration;
418 ///
419 /// # async fn create_config() {
420 /// let config = aws_config::from_env()
421 /// .identity_cache(
422 /// IdentityCache::lazy()
423 /// // Change the load timeout to 10 seconds.
424 /// // Note: there are other timeouts that could trigger if the load timeout is too long.
425 /// .load_timeout(Duration::from_secs(10))
426 /// .build()
427 /// )
428 /// .load()
429 /// .await;
430 /// # }
431 /// ```
432 pub fn identity_cache(
433 mut self,
434 identity_cache: impl ResolveCachedIdentity + 'static,
435 ) -> Self {
436 self.identity_cache = Some(identity_cache.into_shared());
437 self
438 }
439
440 /// Override the credentials provider used to build [`SdkConfig`].
441 ///
442 /// # Examples
443 ///
444 /// Override the credentials provider but load the default value for region:
445 /// ```no_run
446 /// # use aws_credential_types::Credentials;
447 /// # fn create_my_credential_provider() -> Credentials {
448 /// # Credentials::new("example", "example", None, None, "example")
449 /// # }
450 /// # async fn create_config() {
451 /// let config = aws_config::from_env()
452 /// .credentials_provider(create_my_credential_provider())
453 /// .load()
454 /// .await;
455 /// # }
456 /// ```
457 pub fn credentials_provider(
458 mut self,
459 credentials_provider: impl ProvideCredentials + 'static,
460 ) -> Self {
461 self.credentials_provider =
462 TriStateOption::Set(SharedCredentialsProvider::new(credentials_provider));
463 self
464 }
465
466 /// Don't use credentials to sign requests.
467 ///
468 /// Turning off signing with credentials is necessary in some cases, such as using
469 /// anonymous auth for S3, calling operations in STS that don't require a signature,
470 /// or using token-based auth.
471 ///
472 /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
473 /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
474 /// [`test_credentials`](Self::test_credentials).
475 ///
476 /// # Examples
477 ///
478 /// Turn off credentials in order to call a service without signing:
479 /// ```no_run
480 /// # async fn create_config() {
481 /// let config = aws_config::from_env()
482 /// .no_credentials()
483 /// .load()
484 /// .await;
485 /// # }
486 /// ```
487 pub fn no_credentials(mut self) -> Self {
488 self.credentials_provider = TriStateOption::ExplicitlyUnset;
489 self
490 }
491
492 /// Set test credentials for use when signing requests
493 pub fn test_credentials(self) -> Self {
494 #[allow(unused_mut)]
495 let mut ret = self.credentials_provider(Credentials::for_tests());
496 #[cfg(feature = "sso")]
497 {
498 use aws_smithy_runtime_api::client::identity::http::Token;
499 ret = ret.token_provider(Token::for_tests());
500 }
501 ret
502 }
503
504 /// Ignore any environment variables on the host during config resolution
505 ///
506 /// This allows for testing in a reproducible environment that ensures any
507 /// environment variables from the host do not influence environment variable
508 /// resolution.
509 pub fn empty_test_environment(mut self) -> Self {
510 self.env = Some(Env::from_slice(&[]));
511 self
512 }
513
514 /// Override the access token provider used to build [`SdkConfig`].
515 ///
516 /// # Examples
517 ///
518 /// Override the token provider but load the default value for region:
519 /// ```no_run
520 /// # use aws_credential_types::Token;
521 /// # fn create_my_token_provider() -> Token {
522 /// # Token::new("example", None)
523 /// # }
524 /// # async fn create_config() {
525 /// let config = aws_config::from_env()
526 /// .token_provider(create_my_token_provider())
527 /// .load()
528 /// .await;
529 /// # }
530 /// ```
531 pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
532 self.token_provider = Some(SharedTokenProvider::new(token_provider));
533 self
534 }
535
536 /// Override the name of the app used to build [`SdkConfig`].
537 ///
538 /// This _optional_ name is used to identify the application in the user agent header that
539 /// gets sent along with requests.
540 ///
541 /// The app name is selected from an ordered list of sources:
542 /// 1. This override.
543 /// 2. The value of the `AWS_SDK_UA_APP_ID` environment variable.
544 /// 3. Profile files from the key `sdk_ua_app_id`
545 ///
546 /// If none of those sources are set the value is `None` and it is not added to the user agent header.
547 ///
548 /// # Examples
549 /// ```no_run
550 /// # async fn create_config() {
551 /// use aws_config::AppName;
552 /// let config = aws_config::from_env()
553 /// .app_name(AppName::new("my-app-name").expect("valid app name"))
554 /// .load().await;
555 /// # }
556 /// ```
557 pub fn app_name(mut self, app_name: AppName) -> Self {
558 self.app_name = Some(app_name);
559 self
560 }
561
562 /// Provides the ability to programmatically override the profile files that get loaded by the SDK.
563 ///
564 /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in
565 /// `~/.aws/config` and `~/.aws/credentials` respectively.
566 ///
567 /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the
568 /// only requirement being that there is at least one of each. Profile file locations will produce an
569 /// error if they don't exist, but the default config/credentials files paths are exempt from this validation.
570 ///
571 /// # Example: Using a custom profile file path
572 ///
573 /// ```no_run
574 /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
575 /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind};
576 ///
577 /// # async fn example() {
578 /// let profile_files = ProfileFiles::builder()
579 /// .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file")
580 /// .build();
581 /// let sdk_config = aws_config::from_env()
582 /// .profile_files(profile_files)
583 /// .load()
584 /// .await;
585 /// # }
586 #[allow(deprecated)]
587 pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
588 self.profile_files_override = Some(profile_files);
589 self
590 }
591
592 /// Override the profile name used by configuration providers
593 ///
594 /// Profile name is selected from an ordered list of sources:
595 /// 1. This override.
596 /// 2. The value of the `AWS_PROFILE` environment variable.
597 /// 3. `default`
598 ///
599 /// Each AWS profile has a name. For example, in the file below, the profiles are named
600 /// `dev`, `prod` and `staging`:
601 /// ```ini
602 /// [dev]
603 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
604 ///
605 /// [staging]
606 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
607 ///
608 /// [prod]
609 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
610 /// ```
611 ///
612 /// See [Named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
613 /// for more information about naming profiles.
614 ///
615 /// # Example: Using a custom profile name
616 ///
617 /// ```no_run
618 /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
619 ///
620 /// # async fn example() {
621 /// let sdk_config = aws_config::from_env()
622 /// .profile_name("prod")
623 /// .load()
624 /// .await;
625 /// # }
626 pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
627 self.profile_name_override = Some(profile_name.into());
628 self
629 }
630
631 #[doc = docs_for!(account_id_endpoint_mode)]
632 pub fn account_id_endpoint_mode(
633 mut self,
634 account_id_endpoint_mode: AccountIdEndpointMode,
635 ) -> Self {
636 self.account_id_endpoint_mode = Some(account_id_endpoint_mode);
637 self
638 }
639
640 /// Override the endpoint URL used for **all** AWS services.
641 ///
642 /// This method will override the endpoint URL used for **all** AWS services. This primarily
643 /// exists to set a static endpoint for tools like `LocalStack`. When sending requests to
644 /// production AWS services, this method should only be used for service-specific behavior.
645 ///
646 /// When this method is used, the [`Region`](aws_types::region::Region) is only used for signing;
647 /// It is **not** used to route the request.
648 ///
649 /// # Examples
650 ///
651 /// Use a static endpoint for all services
652 /// ```no_run
653 /// # async fn create_config() {
654 /// let sdk_config = aws_config::from_env()
655 /// .endpoint_url("http://localhost:1234")
656 /// .load()
657 /// .await;
658 /// # }
659 pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
660 self.endpoint_url = Some(endpoint_url.into());
661 self
662 }
663
664 #[doc = docs_for!(use_fips)]
665 pub fn use_fips(mut self, use_fips: bool) -> Self {
666 self.use_fips = Some(use_fips);
667 self
668 }
669
670 #[doc = docs_for!(use_dual_stack)]
671 pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
672 self.use_dual_stack = Some(use_dual_stack);
673 self
674 }
675
676 #[doc = docs_for!(disable_request_compression)]
677 pub fn disable_request_compression(mut self, disable_request_compression: bool) -> Self {
678 self.disable_request_compression = Some(disable_request_compression);
679 self
680 }
681
682 #[doc = docs_for!(request_min_compression_size_bytes)]
683 pub fn request_min_compression_size_bytes(mut self, size: u32) -> Self {
684 self.request_min_compression_size_bytes = Some(size);
685 self
686 }
687
688 /// Override the [`StalledStreamProtectionConfig`] used to build [`SdkConfig`].
689 ///
690 /// This configures stalled stream protection. When enabled, download streams
691 /// that stop (stream no data) for longer than a configured grace period will return an error.
692 ///
693 /// By default, streams that transmit less than one byte per-second for five seconds will
694 /// be cancelled.
695 ///
696 /// _Note_: When an override is provided, the default implementation is replaced.
697 ///
698 /// # Examples
699 /// ```no_run
700 /// # async fn create_config() {
701 /// use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
702 /// use std::time::Duration;
703 /// let config = aws_config::from_env()
704 /// .stalled_stream_protection(
705 /// StalledStreamProtectionConfig::enabled()
706 /// .grace_period(Duration::from_secs(1))
707 /// .build()
708 /// )
709 /// .load()
710 /// .await;
711 /// # }
712 /// ```
713 pub fn stalled_stream_protection(
714 mut self,
715 stalled_stream_protection_config: StalledStreamProtectionConfig,
716 ) -> Self {
717 self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
718 self
719 }
720
721 /// Set the checksum calculation strategy to use when making requests.
722 /// # Examples
723 /// ```
724 /// use aws_types::SdkConfig;
725 /// use aws_smithy_types::checksum_config::RequestChecksumCalculation;
726 /// let config = SdkConfig::builder().request_checksum_calculation(RequestChecksumCalculation::WhenSupported).build();
727 /// ```
728 pub fn request_checksum_calculation(
729 mut self,
730 request_checksum_calculation: RequestChecksumCalculation,
731 ) -> Self {
732 self.request_checksum_calculation = Some(request_checksum_calculation);
733 self
734 }
735
736 /// Set the checksum calculation strategy to use for responses.
737 /// # Examples
738 /// ```
739 /// use aws_types::SdkConfig;
740 /// use aws_smithy_types::checksum_config::ResponseChecksumValidation;
741 /// let config = SdkConfig::builder().response_checksum_validation(ResponseChecksumValidation::WhenSupported).build();
742 /// ```
743 pub fn response_checksum_validation(
744 mut self,
745 response_checksum_validation: ResponseChecksumValidation,
746 ) -> Self {
747 self.response_checksum_validation = Some(response_checksum_validation);
748 self
749 }
750
751 /// Load the default configuration chain
752 ///
753 /// If fields have been overridden during builder construction, the override values will be used.
754 ///
755 /// Otherwise, the default values for each field will be provided.
756 ///
757 /// NOTE: When an override is provided, the default implementation is **not** used as a fallback.
758 /// This means that if you provide a region provider that does not return a region, no region will
759 /// be set in the resulting [`SdkConfig`].
760 pub async fn load(self) -> SdkConfig {
761 let time_source = self.time_source.unwrap_or_default();
762
763 let sleep_impl = if self.sleep.is_some() {
764 self.sleep
765 } else {
766 if default_async_sleep().is_none() {
767 tracing::warn!(
768 "An implementation of AsyncSleep was requested by calling default_async_sleep \
769 but no default was set.
770 This happened when ConfigLoader::load was called during Config construction. \
771 You can fix this by setting a sleep_impl on the ConfigLoader before calling \
772 load or by enabling the rt-tokio feature"
773 );
774 }
775 default_async_sleep()
776 };
777
778 let conf = self
779 .provider_config
780 .unwrap_or_else(|| {
781 let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
782 .with_fs(self.fs.unwrap_or_default())
783 .with_env(self.env.unwrap_or_default());
784 if let Some(http_client) = self.http_client.clone() {
785 config = config.with_http_client(http_client);
786 }
787 config
788 })
789 .with_profile_config(self.profile_files_override, self.profile_name_override);
790
791 let use_fips = if let Some(use_fips) = self.use_fips {
792 Some(use_fips)
793 } else {
794 use_fips::use_fips_provider(&conf).await
795 };
796
797 let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
798 Some(use_dual_stack)
799 } else {
800 use_dual_stack::use_dual_stack_provider(&conf).await
801 };
802
803 let conf = conf
804 .with_use_fips(use_fips)
805 .with_use_dual_stack(use_dual_stack);
806
807 let region = if let Some(provider) = self.region {
808 provider.region().await
809 } else {
810 region::Builder::default()
811 .configure(&conf)
812 .build()
813 .region()
814 .await
815 };
816 let conf = conf.with_region(region.clone());
817
818 let retry_config = if let Some(retry_config) = self.retry_config {
819 retry_config
820 } else {
821 retry_config::default_provider()
822 .configure(&conf)
823 .retry_config()
824 .await
825 };
826
827 let app_name = if self.app_name.is_some() {
828 self.app_name
829 } else {
830 app_name::default_provider()
831 .configure(&conf)
832 .app_name()
833 .await
834 };
835
836 let disable_request_compression = if self.disable_request_compression.is_some() {
837 self.disable_request_compression
838 } else {
839 disable_request_compression::disable_request_compression_provider(&conf).await
840 };
841
842 let request_min_compression_size_bytes =
843 if self.request_min_compression_size_bytes.is_some() {
844 self.request_min_compression_size_bytes
845 } else {
846 request_min_compression_size_bytes::request_min_compression_size_bytes_provider(
847 &conf,
848 )
849 .await
850 };
851
852 let base_config = timeout_config::default_provider()
853 .configure(&conf)
854 .timeout_config()
855 .await;
856 let mut timeout_config = self
857 .timeout_config
858 .unwrap_or_else(|| TimeoutConfig::builder().build());
859 timeout_config.take_defaults_from(&base_config);
860
861 let credentials_provider = match self.credentials_provider {
862 TriStateOption::Set(provider) => Some(provider),
863 TriStateOption::NotSet => {
864 let mut builder =
865 credentials::DefaultCredentialsChain::builder().configure(conf.clone());
866 builder.set_region(region.clone());
867 Some(SharedCredentialsProvider::new(builder.build().await))
868 }
869 TriStateOption::ExplicitlyUnset => None,
870 };
871
872 let token_provider = match self.token_provider {
873 Some(provider) => Some(provider),
874 None => {
875 #[cfg(feature = "sso")]
876 {
877 let mut builder =
878 crate::default_provider::token::DefaultTokenChain::builder()
879 .configure(conf.clone());
880 builder.set_region(region.clone());
881 Some(SharedTokenProvider::new(builder.build().await))
882 }
883 #[cfg(not(feature = "sso"))]
884 {
885 None
886 }
887 }
888 };
889
890 let profiles = conf.profile().await;
891 let service_config = EnvServiceConfig {
892 env: conf.env(),
893 env_config_sections: profiles.cloned().unwrap_or_default(),
894 };
895 let mut builder = SdkConfig::builder()
896 .region(region)
897 .retry_config(retry_config)
898 .timeout_config(timeout_config)
899 .time_source(time_source)
900 .service_config(service_config);
901
902 // If an endpoint URL is set programmatically, then our work is done.
903 let endpoint_url = if self.endpoint_url.is_some() {
904 builder.insert_origin("endpoint_url", Origin::shared_config());
905 self.endpoint_url
906 } else {
907 // Otherwise, check to see if we should ignore EP URLs set in the environment.
908 let ignore_configured_endpoint_urls =
909 ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
910 .await
911 .unwrap_or_default();
912
913 if ignore_configured_endpoint_urls {
914 // If yes, log a trace and return `None`.
915 tracing::trace!(
916 "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
917 NOTE: Endpoint URLs set programmatically WILL still be respected"
918 );
919 None
920 } else {
921 // Otherwise, attempt to resolve one.
922 let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
923 builder.insert_origin("endpoint_url", origin);
924 v
925 }
926 };
927
928 builder.set_endpoint_url(endpoint_url);
929 builder.set_behavior_version(self.behavior_version);
930 builder.set_http_client(self.http_client);
931 builder.set_app_name(app_name);
932
933 let identity_cache = match self.identity_cache {
934 None => match self.behavior_version {
935 #[allow(deprecated)]
936 Some(bv) if bv.is_at_least(BehaviorVersion::v2024_03_28()) => {
937 Some(IdentityCache::lazy().build())
938 }
939 _ => None,
940 },
941 Some(user_cache) => Some(user_cache),
942 };
943
944 let request_checksum_calculation =
945 if let Some(request_checksum_calculation) = self.request_checksum_calculation {
946 Some(request_checksum_calculation)
947 } else {
948 checksums::request_checksum_calculation_provider(&conf).await
949 };
950
951 let response_checksum_validation =
952 if let Some(response_checksum_validation) = self.response_checksum_validation {
953 Some(response_checksum_validation)
954 } else {
955 checksums::response_checksum_validation_provider(&conf).await
956 };
957
958 let account_id_endpoint_mode =
959 if let Some(acccount_id_endpoint_mode) = self.account_id_endpoint_mode {
960 Some(acccount_id_endpoint_mode)
961 } else {
962 account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await
963 };
964
965 builder.set_request_checksum_calculation(request_checksum_calculation);
966 builder.set_response_checksum_validation(response_checksum_validation);
967 builder.set_identity_cache(identity_cache);
968 builder.set_credentials_provider(credentials_provider);
969 builder.set_token_provider(token_provider);
970 builder.set_sleep_impl(sleep_impl);
971 builder.set_use_fips(use_fips);
972 builder.set_use_dual_stack(use_dual_stack);
973 builder.set_disable_request_compression(disable_request_compression);
974 builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes);
975 builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
976 builder.set_account_id_endpoint_mode(account_id_endpoint_mode);
977 builder.build()
978 }
979 }
980
981 #[cfg(test)]
982 impl ConfigLoader {
983 pub(crate) fn env(mut self, env: Env) -> Self {
984 self.env = Some(env);
985 self
986 }
987
988 pub(crate) fn fs(mut self, fs: Fs) -> Self {
989 self.fs = Some(fs);
990 self
991 }
992 }
993
994 #[cfg(test)]
995 mod test {
996 #[allow(deprecated)]
997 use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
998 use crate::test_case::{no_traffic_client, InstantSleep};
999 use crate::BehaviorVersion;
1000 use crate::{defaults, ConfigLoader};
1001 use aws_credential_types::provider::ProvideCredentials;
1002 use aws_smithy_async::rt::sleep::TokioSleep;
1003 use aws_smithy_http_client::test_util::{infallible_client_fn, NeverClient};
1004 use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
1005 use aws_types::app_name::AppName;
1006 use aws_types::origin::Origin;
1007 use aws_types::os_shim_internal::{Env, Fs};
1008 use aws_types::sdk_config::{RequestChecksumCalculation, ResponseChecksumValidation};
1009 use std::sync::atomic::{AtomicUsize, Ordering};
1010 use std::sync::Arc;
1011
1012 #[tokio::test]
1013 async fn provider_config_used() {
1014 let (_guard, logs_rx) = capture_test_logs();
1015 let env = Env::from_slice(&[
1016 ("AWS_MAX_ATTEMPTS", "10"),
1017 ("AWS_REGION", "us-west-4"),
1018 ("AWS_ACCESS_KEY_ID", "akid"),
1019 ("AWS_SECRET_ACCESS_KEY", "secret"),
1020 ]);
1021 let fs =
1022 Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
1023 let loader = defaults(BehaviorVersion::latest())
1024 .sleep_impl(TokioSleep::new())
1025 .env(env)
1026 .fs(fs)
1027 .http_client(NeverClient::new())
1028 .profile_name("custom")
1029 .profile_files(
1030 #[allow(deprecated)]
1031 ProfileFiles::builder()
1032 .with_file(
1033 #[allow(deprecated)]
1034 ProfileFileKind::Config,
1035 "test_config",
1036 )
1037 .build(),
1038 )
1039 .load()
1040 .await;
1041 assert_eq!(10, loader.retry_config().unwrap().max_attempts());
1042 assert_eq!("us-west-4", loader.region().unwrap().as_ref());
1043 assert_eq!(
1044 "akid",
1045 loader
1046 .credentials_provider()
1047 .unwrap()
1048 .provide_credentials()
1049 .await
1050 .unwrap()
1051 .access_key_id(),
1052 );
1053 assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
1054
1055 let num_config_loader_logs = logs_rx.contents()
1056 .lines()
1057 // The logger uses fancy formatting, so we have to account for that.
1058 .filter(|l| l.contains("config file loaded \u{1b}[3mpath\u{1b}[0m\u{1b}[2m=\u{1b}[0mSome(\"test_config\") \u{1b}[3msize\u{1b}[0m\u{1b}[2m=\u{1b}"))
1059 .count();
1060
1061 match num_config_loader_logs {
1062 0 => panic!("no config file logs found!"),
1063 1 => (),
1064 more => panic!("the config file was parsed more than once! (parsed {more})",),
1065 };
1066 }
1067
1068 fn base_conf() -> ConfigLoader {
1069 defaults(BehaviorVersion::latest())
1070 .sleep_impl(InstantSleep)
1071 .http_client(no_traffic_client())
1072 }
1073
1074 #[tokio::test]
1075 async fn test_origin_programmatic() {
1076 let _ = tracing_subscriber::fmt::try_init();
1077 let loader = base_conf()
1078 .test_credentials()
1079 .profile_name("custom")
1080 .profile_files(
1081 #[allow(deprecated)]
1082 ProfileFiles::builder()
1083 .with_contents(
1084 #[allow(deprecated)]
1085 ProfileFileKind::Config,
1086 "[profile custom]\nendpoint_url = http://localhost:8989",
1087 )
1088 .build(),
1089 )
1090 .endpoint_url("http://localhost:1111")
1091 .load()
1092 .await;
1093 assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
1094 }
1095
1096 #[tokio::test]
1097 async fn test_origin_env() {
1098 let _ = tracing_subscriber::fmt::try_init();
1099 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
1100 let loader = base_conf()
1101 .test_credentials()
1102 .env(env)
1103 .profile_name("custom")
1104 .profile_files(
1105 #[allow(deprecated)]
1106 ProfileFiles::builder()
1107 .with_contents(
1108 #[allow(deprecated)]
1109 ProfileFileKind::Config,
1110 "[profile custom]\nendpoint_url = http://localhost:8989",
1111 )
1112 .build(),
1113 )
1114 .load()
1115 .await;
1116 assert_eq!(
1117 Origin::shared_environment_variable(),
1118 loader.get_origin("endpoint_url")
1119 );
1120 }
1121
1122 #[tokio::test]
1123 async fn test_origin_fs() {
1124 let _ = tracing_subscriber::fmt::try_init();
1125 let loader = base_conf()
1126 .test_credentials()
1127 .profile_name("custom")
1128 .profile_files(
1129 #[allow(deprecated)]
1130 ProfileFiles::builder()
1131 .with_contents(
1132 #[allow(deprecated)]
1133 ProfileFileKind::Config,
1134 "[profile custom]\nendpoint_url = http://localhost:8989",
1135 )
1136 .build(),
1137 )
1138 .load()
1139 .await;
1140 assert_eq!(
1141 Origin::shared_profile_file(),
1142 loader.get_origin("endpoint_url")
1143 );
1144 }
1145
1146 #[tokio::test]
1147 async fn load_use_fips() {
1148 let conf = base_conf().use_fips(true).load().await;
1149 assert_eq!(Some(true), conf.use_fips());
1150 }
1151
1152 #[tokio::test]
1153 async fn load_dual_stack() {
1154 let conf = base_conf().use_dual_stack(false).load().await;
1155 assert_eq!(Some(false), conf.use_dual_stack());
1156
1157 let conf = base_conf().load().await;
1158 assert_eq!(None, conf.use_dual_stack());
1159 }
1160
1161 #[tokio::test]
1162 async fn load_disable_request_compression() {
1163 let conf = base_conf().disable_request_compression(true).load().await;
1164 assert_eq!(Some(true), conf.disable_request_compression());
1165
1166 let conf = base_conf().load().await;
1167 assert_eq!(None, conf.disable_request_compression());
1168 }
1169
1170 #[tokio::test]
1171 async fn load_request_min_compression_size_bytes() {
1172 let conf = base_conf()
1173 .request_min_compression_size_bytes(99)
1174 .load()
1175 .await;
1176 assert_eq!(Some(99), conf.request_min_compression_size_bytes());
1177
1178 let conf = base_conf().load().await;
1179 assert_eq!(None, conf.request_min_compression_size_bytes());
1180 }
1181
1182 #[tokio::test]
1183 async fn app_name() {
1184 let app_name = AppName::new("my-app-name").unwrap();
1185 let conf = base_conf().app_name(app_name.clone()).load().await;
1186 assert_eq!(Some(&app_name), conf.app_name());
1187 }
1188
1189 #[tokio::test]
1190 async fn request_checksum_calculation() {
1191 let conf = base_conf()
1192 .request_checksum_calculation(RequestChecksumCalculation::WhenRequired)
1193 .load()
1194 .await;
1195 assert_eq!(
1196 Some(RequestChecksumCalculation::WhenRequired),
1197 conf.request_checksum_calculation()
1198 );
1199 }
1200
1201 #[tokio::test]
1202 async fn response_checksum_validation() {
1203 let conf = base_conf()
1204 .response_checksum_validation(ResponseChecksumValidation::WhenRequired)
1205 .load()
1206 .await;
1207 assert_eq!(
1208 Some(ResponseChecksumValidation::WhenRequired),
1209 conf.response_checksum_validation()
1210 );
1211 }
1212
1213 #[cfg(feature = "default-https-client")]
1214 #[tokio::test]
1215 async fn disable_default_credentials() {
1216 let config = defaults(BehaviorVersion::latest())
1217 .no_credentials()
1218 .load()
1219 .await;
1220 assert!(config.credentials_provider().is_none());
1221 }
1222
1223 #[cfg(feature = "default-https-client")]
1224 #[tokio::test]
1225 async fn identity_cache_defaulted() {
1226 let config = defaults(BehaviorVersion::latest()).load().await;
1227
1228 assert!(config.identity_cache().is_some());
1229 }
1230
1231 #[cfg(feature = "default-https-client")]
1232 #[allow(deprecated)]
1233 #[tokio::test]
1234 async fn identity_cache_old_behavior_version() {
1235 let config = defaults(BehaviorVersion::v2023_11_09()).load().await;
1236
1237 assert!(config.identity_cache().is_none());
1238 }
1239
1240 #[tokio::test]
1241 async fn connector_is_shared() {
1242 let num_requests = Arc::new(AtomicUsize::new(0));
1243 let movable = num_requests.clone();
1244 let http_client = infallible_client_fn(move |_req| {
1245 movable.fetch_add(1, Ordering::Relaxed);
1246 http::Response::new("ok!")
1247 });
1248 let config = defaults(BehaviorVersion::latest())
1249 .fs(Fs::from_slice(&[]))
1250 .env(Env::from_slice(&[]))
1251 .http_client(http_client.clone())
1252 .load()
1253 .await;
1254 config
1255 .credentials_provider()
1256 .unwrap()
1257 .provide_credentials()
1258 .await
1259 .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1260 let num_requests = num_requests.load(Ordering::Relaxed);
1261 assert!(num_requests > 0, "{}", num_requests);
1262 }
1263
1264 #[tokio::test]
1265 async fn endpoint_urls_may_be_ignored_from_env() {
1266 let fs = Fs::from_slice(&[(
1267 "test_config",
1268 "[profile custom]\nendpoint_url = http://profile",
1269 )]);
1270 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1271
1272 let conf = base_conf().use_dual_stack(false).load().await;
1273 assert_eq!(Some(false), conf.use_dual_stack());
1274
1275 let conf = base_conf().load().await;
1276 assert_eq!(None, conf.use_dual_stack());
1277
1278 // Check that we get nothing back because the env said we should ignore endpoints
1279 let config = base_conf()
1280 .fs(fs.clone())
1281 .env(env)
1282 .profile_name("custom")
1283 .profile_files(
1284 #[allow(deprecated)]
1285 ProfileFiles::builder()
1286 .with_file(
1287 #[allow(deprecated)]
1288 ProfileFileKind::Config,
1289 "test_config",
1290 )
1291 .build(),
1292 )
1293 .load()
1294 .await;
1295 assert_eq!(None, config.endpoint_url());
1296
1297 // Check that without the env, we DO get something back
1298 let config = base_conf()
1299 .fs(fs)
1300 .profile_name("custom")
1301 .profile_files(
1302 #[allow(deprecated)]
1303 ProfileFiles::builder()
1304 .with_file(
1305 #[allow(deprecated)]
1306 ProfileFileKind::Config,
1307 "test_config",
1308 )
1309 .build(),
1310 )
1311 .load()
1312 .await;
1313 assert_eq!(Some("http://profile"), config.endpoint_url());
1314 }
1315
1316 #[tokio::test]
1317 async fn endpoint_urls_may_be_ignored_from_profile() {
1318 let fs = Fs::from_slice(&[(
1319 "test_config",
1320 "[profile custom]\nignore_configured_endpoint_urls = true",
1321 )]);
1322 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1323
1324 // Check that we get nothing back because the profile said we should ignore endpoints
1325 let config = base_conf()
1326 .fs(fs)
1327 .env(env.clone())
1328 .profile_name("custom")
1329 .profile_files(
1330 #[allow(deprecated)]
1331 ProfileFiles::builder()
1332 .with_file(
1333 #[allow(deprecated)]
1334 ProfileFileKind::Config,
1335 "test_config",
1336 )
1337 .build(),
1338 )
1339 .load()
1340 .await;
1341 assert_eq!(None, config.endpoint_url());
1342
1343 // Check that without the profile, we DO get something back
1344 let config = base_conf().env(env).load().await;
1345 assert_eq!(Some("http://environment"), config.endpoint_url());
1346 }
1347
1348 #[tokio::test]
1349 async fn programmatic_endpoint_urls_may_not_be_ignored() {
1350 let fs = Fs::from_slice(&[(
1351 "test_config",
1352 "[profile custom]\nignore_configured_endpoint_urls = true",
1353 )]);
1354 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1355
1356 // Check that we get something back because we explicitly set the loader's endpoint URL
1357 let config = base_conf()
1358 .fs(fs)
1359 .env(env)
1360 .endpoint_url("http://localhost")
1361 .profile_name("custom")
1362 .profile_files(
1363 #[allow(deprecated)]
1364 ProfileFiles::builder()
1365 .with_file(
1366 #[allow(deprecated)]
1367 ProfileFileKind::Config,
1368 "test_config",
1369 )
1370 .build(),
1371 )
1372 .load()
1373 .await;
1374 assert_eq!(Some("http://localhost"), config.endpoint_url());
1375 }
1376 }
1377}