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/// # Examples
149/// ```no_run
150/// # async fn create_config() {
151/// let config = aws_config::from_env().region("us-east-1").load().await;
152/// # }
153/// ```
154#[cfg(feature = "behavior-version-latest")]
155pub fn from_env() -> ConfigLoader {
156    ConfigLoader::default().behavior_version(BehaviorVersion::latest())
157}
158
159/// Load default configuration with the _latest_ defaults.
160///
161/// Convenience wrapper equivalent to `aws_config::load_defaults(BehaviorVersion::latest()).await`
162#[cfg(feature = "behavior-version-latest")]
163pub async fn load_from_env() -> SdkConfig {
164    from_env().load().await
165}
166
167/// Create a config loader with the _latest_ defaults.
168#[cfg(not(feature = "behavior-version-latest"))]
169#[deprecated(
170    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."
171)]
172pub fn from_env() -> ConfigLoader {
173    ConfigLoader::default().behavior_version(BehaviorVersion::latest())
174}
175
176/// Load default configuration with the _latest_ defaults.
177#[cfg(not(feature = "behavior-version-latest"))]
178#[deprecated(
179    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."
180)]
181pub async fn load_from_env() -> SdkConfig {
182    load_defaults(BehaviorVersion::latest()).await
183}
184
185/// Create a config loader with the defaults for the given behavior version.
186///
187/// # Examples
188/// ```no_run
189/// # async fn create_config() {
190/// use aws_config::BehaviorVersion;
191/// let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
192///     .region("us-east-1")
193///     .load()
194///     .await;
195/// # }
196/// ```
197pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
198    ConfigLoader::default().behavior_version(version)
199}
200
201/// Load default configuration with the given behavior version.
202///
203/// Convenience wrapper equivalent to `aws_config::defaults(behavior_version).load().await`
204pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
205    defaults(version).load().await
206}
207
208mod loader {
209    use crate::env_service_config::EnvServiceConfig;
210    use aws_credential_types::provider::{
211        token::{ProvideToken, SharedTokenProvider},
212        ProvideCredentials, SharedCredentialsProvider,
213    };
214    use aws_credential_types::Credentials;
215    use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
216    use aws_smithy_async::time::{SharedTimeSource, TimeSource};
217    use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
218    use aws_smithy_runtime_api::client::http::HttpClient;
219    use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
220    use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
221    use aws_smithy_runtime_api::shared::IntoShared;
222    use aws_smithy_types::retry::RetryConfig;
223    use aws_smithy_types::timeout::TimeoutConfig;
224    use aws_types::app_name::AppName;
225    use aws_types::docs_for;
226    use aws_types::origin::Origin;
227    use aws_types::os_shim_internal::{Env, Fs};
228    use aws_types::sdk_config::SharedHttpClient;
229    use aws_types::SdkConfig;
230
231    use crate::default_provider::{
232        app_name, credentials, endpoint_url, ignore_configured_endpoint_urls as ignore_ep, region,
233        retry_config, timeout_config, use_dual_stack, use_fips,
234    };
235    use crate::meta::region::ProvideRegion;
236    #[allow(deprecated)]
237    use crate::profile::profile_file::ProfileFiles;
238    use crate::provider_config::ProviderConfig;
239
240    #[derive(Default, Debug)]
241    enum CredentialsProviderOption {
242        /// No provider was set by the user. We can set up the default credentials provider chain.
243        #[default]
244        NotSet,
245        /// The credentials provider was explicitly unset. Do not set up a default chain.
246        ExplicitlyUnset,
247        /// Use the given credentials provider.
248        Set(SharedCredentialsProvider),
249    }
250
251    /// Load a cross-service [`SdkConfig`] from the environment
252    ///
253    /// This builder supports overriding individual components of the generated config. Overriding a component
254    /// will skip the standard resolution chain from **for that component**. For example,
255    /// if you override the region provider, _even if that provider returns None_, the default region provider
256    /// chain will not be used.
257    #[derive(Default, Debug)]
258    pub struct ConfigLoader {
259        app_name: Option<AppName>,
260        identity_cache: Option<SharedIdentityCache>,
261        credentials_provider: CredentialsProviderOption,
262        token_provider: Option<SharedTokenProvider>,
263        endpoint_url: Option<String>,
264        region: Option<Box<dyn ProvideRegion>>,
265        retry_config: Option<RetryConfig>,
266        sleep: Option<SharedAsyncSleep>,
267        timeout_config: Option<TimeoutConfig>,
268        provider_config: Option<ProviderConfig>,
269        http_client: Option<SharedHttpClient>,
270        profile_name_override: Option<String>,
271        #[allow(deprecated)]
272        profile_files_override: Option<ProfileFiles>,
273        use_fips: Option<bool>,
274        use_dual_stack: Option<bool>,
275        time_source: Option<SharedTimeSource>,
276        stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
277        env: Option<Env>,
278        fs: Option<Fs>,
279        behavior_version: Option<BehaviorVersion>,
280    }
281
282    impl ConfigLoader {
283        /// Sets the [`BehaviorVersion`] used to build [`SdkConfig`].
284        pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
285            self.behavior_version = Some(behavior_version);
286            self
287        }
288
289        /// Override the region used to build [`SdkConfig`].
290        ///
291        /// # Examples
292        /// ```no_run
293        /// # async fn create_config() {
294        /// use aws_types::region::Region;
295        /// let config = aws_config::from_env()
296        ///     .region(Region::new("us-east-1"))
297        ///     .load().await;
298        /// # }
299        /// ```
300        pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
301            self.region = Some(Box::new(region));
302            self
303        }
304
305        /// Override the retry_config used to build [`SdkConfig`].
306        ///
307        /// # Examples
308        /// ```no_run
309        /// # async fn create_config() {
310        /// use aws_config::retry::RetryConfig;
311        ///
312        /// let config = aws_config::from_env()
313        ///     .retry_config(RetryConfig::standard().with_max_attempts(2))
314        ///     .load()
315        ///     .await;
316        /// # }
317        /// ```
318        pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
319            self.retry_config = Some(retry_config);
320            self
321        }
322
323        /// Override the timeout config used to build [`SdkConfig`].
324        ///
325        /// This will be merged with timeouts coming from the timeout information provider, which
326        /// currently includes a default `CONNECT` timeout of `3.1s`.
327        ///
328        /// If you want to disable timeouts, use [`TimeoutConfig::disabled`]. If you want to disable
329        /// a specific timeout, use `TimeoutConfig::set_<type>(None)`.
330        ///
331        /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials
332        /// provider chain are configured separately.
333        ///
334        /// # Examples
335        /// ```no_run
336        /// # use std::time::Duration;
337        /// # async fn create_config() {
338        /// use aws_config::timeout::TimeoutConfig;
339        ///
340        /// let config = aws_config::from_env()
341        ///    .timeout_config(
342        ///        TimeoutConfig::builder()
343        ///            .operation_timeout(Duration::from_secs(5))
344        ///            .build()
345        ///    )
346        ///    .load()
347        ///    .await;
348        /// # }
349        /// ```
350        pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
351            self.timeout_config = Some(timeout_config);
352            self
353        }
354
355        /// Override the sleep implementation for this [`ConfigLoader`].
356        ///
357        /// The sleep implementation is used to create timeout futures.
358        /// You generally won't need to change this unless you're using an async runtime other
359        /// than Tokio.
360        pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
361            // it's possible that we could wrapping an `Arc in an `Arc` and that's OK
362            self.sleep = Some(sleep.into_shared());
363            self
364        }
365
366        /// Set the time source used for tasks like signing requests.
367        ///
368        /// You generally won't need to change this unless you're compiling for a target
369        /// that can't provide a default, such as WASM, or unless you're writing a test against
370        /// the client that needs a fixed time.
371        pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
372            self.time_source = Some(time_source.into_shared());
373            self
374        }
375
376        /// Override the [`HttpClient`] for this [`ConfigLoader`].
377        ///
378        /// The HTTP client will be used for both AWS services and credentials providers.
379        ///
380        /// If you wish to use a separate HTTP client for credentials providers when creating clients,
381        /// then override the HTTP client set with this function on the client-specific `Config`s.
382        ///
383        /// ## Examples
384        ///
385        /// ```no_run
386        /// # use aws_smithy_async::rt::sleep::SharedAsyncSleep;
387        /// #[cfg(feature = "client-hyper")]
388        /// # async fn create_config() {
389        /// use std::time::Duration;
390        /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
391        ///
392        /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new()
393        ///     .with_webpki_roots()
394        ///     // NOTE: setting `https_only()` will not allow this connector to work with IMDS.
395        ///     .https_only()
396        ///     .enable_http1()
397        ///     .enable_http2()
398        ///     .build();
399        ///
400        /// let hyper_client = HyperClientBuilder::new().build(tls_connector);
401        /// let sdk_config = aws_config::from_env()
402        ///     .http_client(hyper_client)
403        ///     .load()
404        ///     .await;
405        /// # }
406        /// ```
407        pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
408            self.http_client = Some(http_client.into_shared());
409            self
410        }
411
412        /// Override the identity cache used to build [`SdkConfig`].
413        ///
414        /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
415        /// that will load credentials upon first request, cache them, and then reload them during
416        /// another request when they are close to expiring.
417        ///
418        /// # Examples
419        ///
420        /// Change a setting on the default lazy caching implementation:
421        /// ```no_run
422        /// use aws_config::identity::IdentityCache;
423        /// use std::time::Duration;
424        ///
425        /// # async fn create_config() {
426        /// let config = aws_config::from_env()
427        ///     .identity_cache(
428        ///         IdentityCache::lazy()
429        ///             // Change the load timeout to 10 seconds.
430        ///             // Note: there are other timeouts that could trigger if the load timeout is too long.
431        ///             .load_timeout(Duration::from_secs(10))
432        ///             .build()
433        ///     )
434        ///     .load()
435        ///     .await;
436        /// # }
437        /// ```
438        pub fn identity_cache(
439            mut self,
440            identity_cache: impl ResolveCachedIdentity + 'static,
441        ) -> Self {
442            self.identity_cache = Some(identity_cache.into_shared());
443            self
444        }
445
446        /// Override the credentials provider used to build [`SdkConfig`].
447        ///
448        /// # Examples
449        ///
450        /// Override the credentials provider but load the default value for region:
451        /// ```no_run
452        /// # use aws_credential_types::Credentials;
453        /// # fn create_my_credential_provider() -> Credentials {
454        /// #     Credentials::new("example", "example", None, None, "example")
455        /// # }
456        /// # async fn create_config() {
457        /// let config = aws_config::from_env()
458        ///     .credentials_provider(create_my_credential_provider())
459        ///     .load()
460        ///     .await;
461        /// # }
462        /// ```
463        pub fn credentials_provider(
464            mut self,
465            credentials_provider: impl ProvideCredentials + 'static,
466        ) -> Self {
467            self.credentials_provider = CredentialsProviderOption::Set(
468                SharedCredentialsProvider::new(credentials_provider),
469            );
470            self
471        }
472
473        /// Don't use credentials to sign requests.
474        ///
475        /// Turning off signing with credentials is necessary in some cases, such as using
476        /// anonymous auth for S3, calling operations in STS that don't require a signature,
477        /// or using token-based auth.
478        ///
479        /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
480        /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
481        /// [`test_credentials`](Self::test_credentials).
482        ///
483        /// # Examples
484        ///
485        /// Turn off credentials in order to call a service without signing:
486        /// ```no_run
487        /// # async fn create_config() {
488        /// let config = aws_config::from_env()
489        ///     .no_credentials()
490        ///     .load()
491        ///     .await;
492        /// # }
493        /// ```
494        pub fn no_credentials(mut self) -> Self {
495            self.credentials_provider = CredentialsProviderOption::ExplicitlyUnset;
496            self
497        }
498
499        /// Set test credentials for use when signing requests
500        pub fn test_credentials(self) -> Self {
501            #[allow(unused_mut)]
502            let mut ret = self.credentials_provider(Credentials::for_tests());
503            #[cfg(all(feature = "sso", feature = "test-util"))]
504            {
505                use aws_smithy_runtime_api::client::identity::http::Token;
506                ret = ret.token_provider(Token::for_tests());
507            }
508            ret
509        }
510
511        /// Override the access token provider used to build [`SdkConfig`].
512        ///
513        /// # Examples
514        ///
515        /// Override the token provider but load the default value for region:
516        /// ```no_run
517        /// # use aws_credential_types::Token;
518        /// # fn create_my_token_provider() -> Token {
519        /// #     Token::new("example", None)
520        /// # }
521        /// # async fn create_config() {
522        /// let config = aws_config::from_env()
523        ///     .token_provider(create_my_token_provider())
524        ///     .load()
525        ///     .await;
526        /// # }
527        /// ```
528        pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
529            self.token_provider = Some(SharedTokenProvider::new(token_provider));
530            self
531        }
532
533        /// Override the name of the app used to build [`SdkConfig`].
534        ///
535        /// This _optional_ name is used to identify the application in the user agent that
536        /// gets sent along with requests.
537        ///
538        /// # Examples
539        /// ```no_run
540        /// # async fn create_config() {
541        /// use aws_config::AppName;
542        /// let config = aws_config::from_env()
543        ///     .app_name(AppName::new("my-app-name").expect("valid app name"))
544        ///     .load().await;
545        /// # }
546        /// ```
547        pub fn app_name(mut self, app_name: AppName) -> Self {
548            self.app_name = Some(app_name);
549            self
550        }
551
552        /// Provides the ability to programmatically override the profile files that get loaded by the SDK.
553        ///
554        /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in
555        /// `~/.aws/config` and `~/.aws/credentials` respectively.
556        ///
557        /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the
558        /// only requirement being that there is at least one of each. Profile file locations will produce an
559        /// error if they don't exist, but the default config/credentials files paths are exempt from this validation.
560        ///
561        /// # Example: Using a custom profile file path
562        ///
563        /// ```no_run
564        /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
565        /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind};
566        ///
567        /// # async fn example() {
568        /// let profile_files = ProfileFiles::builder()
569        ///     .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file")
570        ///     .build();
571        /// let sdk_config = aws_config::from_env()
572        ///     .profile_files(profile_files)
573        ///     .load()
574        ///     .await;
575        /// # }
576        #[allow(deprecated)]
577        pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
578            self.profile_files_override = Some(profile_files);
579            self
580        }
581
582        /// Override the profile name used by configuration providers
583        ///
584        /// Profile name is selected from an ordered list of sources:
585        /// 1. This override.
586        /// 2. The value of the `AWS_PROFILE` environment variable.
587        /// 3. `default`
588        ///
589        /// Each AWS profile has a name. For example, in the file below, the profiles are named
590        /// `dev`, `prod` and `staging`:
591        /// ```ini
592        /// [dev]
593        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
594        ///
595        /// [staging]
596        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
597        ///
598        /// [prod]
599        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
600        /// ```
601        ///
602        /// See [Named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
603        /// for more information about naming profiles.
604        ///
605        /// # Example: Using a custom profile name
606        ///
607        /// ```no_run
608        /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
609        ///
610        /// # async fn example() {
611        /// let sdk_config = aws_config::from_env()
612        ///     .profile_name("prod")
613        ///     .load()
614        ///     .await;
615        /// # }
616        pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
617            self.profile_name_override = Some(profile_name.into());
618            self
619        }
620
621        /// Override the endpoint URL used for **all** AWS services.
622        ///
623        /// This method will override the endpoint URL used for **all** AWS services. This primarily
624        /// exists to set a static endpoint for tools like `LocalStack`. When sending requests to
625        /// production AWS services, this method should only be used for service-specific behavior.
626        ///
627        /// When this method is used, the [`Region`](aws_types::region::Region) is only used for signing;
628        /// It is **not** used to route the request.
629        ///
630        /// # Examples
631        ///
632        /// Use a static endpoint for all services
633        /// ```no_run
634        /// # async fn create_config() {
635        /// let sdk_config = aws_config::from_env()
636        ///     .endpoint_url("http://localhost:1234")
637        ///     .load()
638        ///     .await;
639        /// # }
640        pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
641            self.endpoint_url = Some(endpoint_url.into());
642            self
643        }
644
645        #[doc = docs_for!(use_fips)]
646        pub fn use_fips(mut self, use_fips: bool) -> Self {
647            self.use_fips = Some(use_fips);
648            self
649        }
650
651        #[doc = docs_for!(use_dual_stack)]
652        pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
653            self.use_dual_stack = Some(use_dual_stack);
654            self
655        }
656
657        /// Override the [`StalledStreamProtectionConfig`] used to build [`SdkConfig`].
658        ///
659        /// This configures stalled stream protection. When enabled, download streams
660        /// that stop (stream no data) for longer than a configured grace period will return an error.
661        ///
662        /// By default, streams that transmit less than one byte per-second for five seconds will
663        /// be cancelled.
664        ///
665        /// _Note_: When an override is provided, the default implementation is replaced.
666        ///
667        /// # Examples
668        /// ```no_run
669        /// # async fn create_config() {
670        /// use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
671        /// use std::time::Duration;
672        /// let config = aws_config::from_env()
673        ///     .stalled_stream_protection(
674        ///         StalledStreamProtectionConfig::enabled()
675        ///             .grace_period(Duration::from_secs(1))
676        ///             .build()
677        ///     )
678        ///     .load()
679        ///     .await;
680        /// # }
681        /// ```
682        pub fn stalled_stream_protection(
683            mut self,
684            stalled_stream_protection_config: StalledStreamProtectionConfig,
685        ) -> Self {
686            self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
687            self
688        }
689
690        /// Load the default configuration chain
691        ///
692        /// If fields have been overridden during builder construction, the override values will be used.
693        ///
694        /// Otherwise, the default values for each field will be provided.
695        ///
696        /// NOTE: When an override is provided, the default implementation is **not** used as a fallback.
697        /// This means that if you provide a region provider that does not return a region, no region will
698        /// be set in the resulting [`SdkConfig`].
699        pub async fn load(self) -> SdkConfig {
700            let time_source = self.time_source.unwrap_or_default();
701
702            let sleep_impl = if self.sleep.is_some() {
703                self.sleep
704            } else {
705                if default_async_sleep().is_none() {
706                    tracing::warn!(
707                        "An implementation of AsyncSleep was requested by calling default_async_sleep \
708                         but no default was set.
709                         This happened when ConfigLoader::load was called during Config construction. \
710                         You can fix this by setting a sleep_impl on the ConfigLoader before calling \
711                         load or by enabling the rt-tokio feature"
712                    );
713                }
714                default_async_sleep()
715            };
716
717            let conf = self
718                .provider_config
719                .unwrap_or_else(|| {
720                    let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
721                        .with_fs(self.fs.unwrap_or_default())
722                        .with_env(self.env.unwrap_or_default());
723                    if let Some(http_client) = self.http_client.clone() {
724                        config = config.with_http_client(http_client);
725                    }
726                    config
727                })
728                .with_profile_config(self.profile_files_override, self.profile_name_override);
729
730            let use_fips = if let Some(use_fips) = self.use_fips {
731                Some(use_fips)
732            } else {
733                use_fips::use_fips_provider(&conf).await
734            };
735
736            let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
737                Some(use_dual_stack)
738            } else {
739                use_dual_stack::use_dual_stack_provider(&conf).await
740            };
741
742            let conf = conf
743                .with_use_fips(use_fips)
744                .with_use_dual_stack(use_dual_stack);
745
746            let region = if let Some(provider) = self.region {
747                provider.region().await
748            } else {
749                region::Builder::default()
750                    .configure(&conf)
751                    .build()
752                    .region()
753                    .await
754            };
755
756            let retry_config = if let Some(retry_config) = self.retry_config {
757                retry_config
758            } else {
759                retry_config::default_provider()
760                    .configure(&conf)
761                    .retry_config()
762                    .await
763            };
764
765            let app_name = if self.app_name.is_some() {
766                self.app_name
767            } else {
768                app_name::default_provider()
769                    .configure(&conf)
770                    .app_name()
771                    .await
772            };
773
774            let base_config = timeout_config::default_provider()
775                .configure(&conf)
776                .timeout_config()
777                .await;
778            let mut timeout_config = self
779                .timeout_config
780                .unwrap_or_else(|| TimeoutConfig::builder().build());
781            timeout_config.take_defaults_from(&base_config);
782
783            let credentials_provider = match self.credentials_provider {
784                CredentialsProviderOption::Set(provider) => Some(provider),
785                CredentialsProviderOption::NotSet => {
786                    let mut builder =
787                        credentials::DefaultCredentialsChain::builder().configure(conf.clone());
788                    builder.set_region(region.clone());
789                    Some(SharedCredentialsProvider::new(builder.build().await))
790                }
791                CredentialsProviderOption::ExplicitlyUnset => None,
792            };
793
794            let token_provider = match self.token_provider {
795                Some(provider) => Some(provider),
796                None => {
797                    #[cfg(feature = "sso")]
798                    {
799                        let mut builder =
800                            crate::default_provider::token::DefaultTokenChain::builder()
801                                .configure(conf.clone());
802                        builder.set_region(region.clone());
803                        Some(SharedTokenProvider::new(builder.build().await))
804                    }
805                    #[cfg(not(feature = "sso"))]
806                    {
807                        None
808                    }
809                }
810            };
811
812            let profiles = conf.profile().await;
813            let service_config = EnvServiceConfig {
814                env: conf.env(),
815                env_config_sections: profiles.cloned().unwrap_or_default(),
816            };
817            let mut builder = SdkConfig::builder()
818                .region(region)
819                .retry_config(retry_config)
820                .timeout_config(timeout_config)
821                .time_source(time_source)
822                .service_config(service_config);
823
824            // If an endpoint URL is set programmatically, then our work is done.
825            let endpoint_url = if self.endpoint_url.is_some() {
826                builder.insert_origin("endpoint_url", Origin::shared_config());
827                self.endpoint_url
828            } else {
829                // Otherwise, check to see if we should ignore EP URLs set in the environment.
830                let ignore_configured_endpoint_urls =
831                    ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
832                        .await
833                        .unwrap_or_default();
834
835                if ignore_configured_endpoint_urls {
836                    // If yes, log a trace and return `None`.
837                    tracing::trace!(
838                        "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
839                        NOTE: Endpoint URLs set programmatically WILL still be respected"
840                    );
841                    None
842                } else {
843                    // Otherwise, attempt to resolve one.
844                    let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
845                    builder.insert_origin("endpoint_url", origin);
846                    v
847                }
848            };
849            builder.set_endpoint_url(endpoint_url);
850
851            builder.set_behavior_version(self.behavior_version);
852            builder.set_http_client(self.http_client);
853            builder.set_app_name(app_name);
854            builder.set_identity_cache(self.identity_cache);
855            builder.set_credentials_provider(credentials_provider);
856            builder.set_token_provider(token_provider);
857            builder.set_sleep_impl(sleep_impl);
858            builder.set_use_fips(use_fips);
859            builder.set_use_dual_stack(use_dual_stack);
860            builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
861            builder.build()
862        }
863    }
864
865    #[cfg(test)]
866    impl ConfigLoader {
867        pub(crate) fn env(mut self, env: Env) -> Self {
868            self.env = Some(env);
869            self
870        }
871
872        pub(crate) fn fs(mut self, fs: Fs) -> Self {
873            self.fs = Some(fs);
874            self
875        }
876    }
877
878    #[cfg(test)]
879    mod test {
880        #[allow(deprecated)]
881        use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
882        use crate::test_case::{no_traffic_client, InstantSleep};
883        use crate::BehaviorVersion;
884        use crate::{defaults, ConfigLoader};
885        use aws_credential_types::provider::ProvideCredentials;
886        use aws_smithy_async::rt::sleep::TokioSleep;
887        use aws_smithy_runtime::client::http::test_util::{infallible_client_fn, NeverClient};
888        use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
889        use aws_types::app_name::AppName;
890        use aws_types::origin::Origin;
891        use aws_types::os_shim_internal::{Env, Fs};
892        use std::sync::atomic::{AtomicUsize, Ordering};
893        use std::sync::Arc;
894
895        #[tokio::test]
896        async fn provider_config_used() {
897            let (_guard, logs_rx) = capture_test_logs();
898            let env = Env::from_slice(&[
899                ("AWS_MAX_ATTEMPTS", "10"),
900                ("AWS_REGION", "us-west-4"),
901                ("AWS_ACCESS_KEY_ID", "akid"),
902                ("AWS_SECRET_ACCESS_KEY", "secret"),
903            ]);
904            let fs =
905                Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
906            let loader = defaults(BehaviorVersion::latest())
907                .sleep_impl(TokioSleep::new())
908                .env(env)
909                .fs(fs)
910                .http_client(NeverClient::new())
911                .profile_name("custom")
912                .profile_files(
913                    #[allow(deprecated)]
914                    ProfileFiles::builder()
915                        .with_file(
916                            #[allow(deprecated)]
917                            ProfileFileKind::Config,
918                            "test_config",
919                        )
920                        .build(),
921                )
922                .load()
923                .await;
924            assert_eq!(10, loader.retry_config().unwrap().max_attempts());
925            assert_eq!("us-west-4", loader.region().unwrap().as_ref());
926            assert_eq!(
927                "akid",
928                loader
929                    .credentials_provider()
930                    .unwrap()
931                    .provide_credentials()
932                    .await
933                    .unwrap()
934                    .access_key_id(),
935            );
936            assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
937
938            let num_config_loader_logs = logs_rx.contents()
939                .lines()
940                // The logger uses fancy formatting, so we have to account for that.
941                .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}"))
942                .count();
943
944            match num_config_loader_logs {
945                0 => panic!("no config file logs found!"),
946                1 => (),
947                more => panic!("the config file was parsed more than once! (parsed {more})",),
948            };
949        }
950
951        fn base_conf() -> ConfigLoader {
952            defaults(BehaviorVersion::latest())
953                .sleep_impl(InstantSleep)
954                .http_client(no_traffic_client())
955        }
956
957        #[tokio::test]
958        async fn test_origin_programmatic() {
959            let _ = tracing_subscriber::fmt::try_init();
960            let loader = base_conf()
961                .test_credentials()
962                .profile_name("custom")
963                .profile_files(
964                    #[allow(deprecated)]
965                    ProfileFiles::builder()
966                        .with_contents(
967                            #[allow(deprecated)]
968                            ProfileFileKind::Config,
969                            "[profile custom]\nendpoint_url = http://localhost:8989",
970                        )
971                        .build(),
972                )
973                .endpoint_url("http://localhost:1111")
974                .load()
975                .await;
976            assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
977        }
978
979        #[tokio::test]
980        async fn test_origin_env() {
981            let _ = tracing_subscriber::fmt::try_init();
982            let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
983            let loader = base_conf()
984                .test_credentials()
985                .env(env)
986                .profile_name("custom")
987                .profile_files(
988                    #[allow(deprecated)]
989                    ProfileFiles::builder()
990                        .with_contents(
991                            #[allow(deprecated)]
992                            ProfileFileKind::Config,
993                            "[profile custom]\nendpoint_url = http://localhost:8989",
994                        )
995                        .build(),
996                )
997                .load()
998                .await;
999            assert_eq!(
1000                Origin::shared_environment_variable(),
1001                loader.get_origin("endpoint_url")
1002            );
1003        }
1004
1005        #[tokio::test]
1006        async fn test_origin_fs() {
1007            let _ = tracing_subscriber::fmt::try_init();
1008            let loader = base_conf()
1009                .test_credentials()
1010                .profile_name("custom")
1011                .profile_files(
1012                    #[allow(deprecated)]
1013                    ProfileFiles::builder()
1014                        .with_contents(
1015                            #[allow(deprecated)]
1016                            ProfileFileKind::Config,
1017                            "[profile custom]\nendpoint_url = http://localhost:8989",
1018                        )
1019                        .build(),
1020                )
1021                .load()
1022                .await;
1023            assert_eq!(
1024                Origin::shared_profile_file(),
1025                loader.get_origin("endpoint_url")
1026            );
1027        }
1028
1029        #[tokio::test]
1030        async fn load_fips() {
1031            let conf = base_conf().use_fips(true).load().await;
1032            assert_eq!(Some(true), conf.use_fips());
1033        }
1034
1035        #[tokio::test]
1036        async fn load_dual_stack() {
1037            let conf = base_conf().use_dual_stack(false).load().await;
1038            assert_eq!(Some(false), conf.use_dual_stack());
1039
1040            let conf = base_conf().load().await;
1041            assert_eq!(None, conf.use_dual_stack());
1042        }
1043
1044        #[tokio::test]
1045        async fn app_name() {
1046            let app_name = AppName::new("my-app-name").unwrap();
1047            let conf = base_conf().app_name(app_name.clone()).load().await;
1048            assert_eq!(Some(&app_name), conf.app_name());
1049        }
1050
1051        #[cfg(feature = "rustls")]
1052        #[tokio::test]
1053        async fn disable_default_credentials() {
1054            let config = defaults(BehaviorVersion::latest())
1055                .no_credentials()
1056                .load()
1057                .await;
1058            assert!(config.identity_cache().is_none());
1059            assert!(config.credentials_provider().is_none());
1060        }
1061
1062        #[tokio::test]
1063        async fn connector_is_shared() {
1064            let num_requests = Arc::new(AtomicUsize::new(0));
1065            let movable = num_requests.clone();
1066            let http_client = infallible_client_fn(move |_req| {
1067                movable.fetch_add(1, Ordering::Relaxed);
1068                http::Response::new("ok!")
1069            });
1070            let config = defaults(BehaviorVersion::latest())
1071                .fs(Fs::from_slice(&[]))
1072                .env(Env::from_slice(&[]))
1073                .http_client(http_client.clone())
1074                .load()
1075                .await;
1076            config
1077                .credentials_provider()
1078                .unwrap()
1079                .provide_credentials()
1080                .await
1081                .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1082            let num_requests = num_requests.load(Ordering::Relaxed);
1083            assert!(num_requests > 0, "{}", num_requests);
1084        }
1085
1086        #[tokio::test]
1087        async fn endpoint_urls_may_be_ignored_from_env() {
1088            let fs = Fs::from_slice(&[(
1089                "test_config",
1090                "[profile custom]\nendpoint_url = http://profile",
1091            )]);
1092            let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1093
1094            let conf = base_conf().use_dual_stack(false).load().await;
1095            assert_eq!(Some(false), conf.use_dual_stack());
1096
1097            let conf = base_conf().load().await;
1098            assert_eq!(None, conf.use_dual_stack());
1099
1100            // Check that we get nothing back because the env said we should ignore endpoints
1101            let config = base_conf()
1102                .fs(fs.clone())
1103                .env(env)
1104                .profile_name("custom")
1105                .profile_files(
1106                    #[allow(deprecated)]
1107                    ProfileFiles::builder()
1108                        .with_file(
1109                            #[allow(deprecated)]
1110                            ProfileFileKind::Config,
1111                            "test_config",
1112                        )
1113                        .build(),
1114                )
1115                .load()
1116                .await;
1117            assert_eq!(None, config.endpoint_url());
1118
1119            // Check that without the env, we DO get something back
1120            let config = base_conf()
1121                .fs(fs)
1122                .profile_name("custom")
1123                .profile_files(
1124                    #[allow(deprecated)]
1125                    ProfileFiles::builder()
1126                        .with_file(
1127                            #[allow(deprecated)]
1128                            ProfileFileKind::Config,
1129                            "test_config",
1130                        )
1131                        .build(),
1132                )
1133                .load()
1134                .await;
1135            assert_eq!(Some("http://profile"), config.endpoint_url());
1136        }
1137
1138        #[tokio::test]
1139        async fn endpoint_urls_may_be_ignored_from_profile() {
1140            let fs = Fs::from_slice(&[(
1141                "test_config",
1142                "[profile custom]\nignore_configured_endpoint_urls = true",
1143            )]);
1144            let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1145
1146            // Check that we get nothing back because the profile said we should ignore endpoints
1147            let config = base_conf()
1148                .fs(fs)
1149                .env(env.clone())
1150                .profile_name("custom")
1151                .profile_files(
1152                    #[allow(deprecated)]
1153                    ProfileFiles::builder()
1154                        .with_file(
1155                            #[allow(deprecated)]
1156                            ProfileFileKind::Config,
1157                            "test_config",
1158                        )
1159                        .build(),
1160                )
1161                .load()
1162                .await;
1163            assert_eq!(None, config.endpoint_url());
1164
1165            // Check that without the profile, we DO get something back
1166            let config = base_conf().env(env).load().await;
1167            assert_eq!(Some("http://environment"), config.endpoint_url());
1168        }
1169
1170        #[tokio::test]
1171        async fn programmatic_endpoint_urls_may_not_be_ignored() {
1172            let fs = Fs::from_slice(&[(
1173                "test_config",
1174                "[profile custom]\nignore_configured_endpoint_urls = true",
1175            )]);
1176            let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1177
1178            // Check that we get something back because we explicitly set the loader's endpoint URL
1179            let config = base_conf()
1180                .fs(fs)
1181                .env(env)
1182                .endpoint_url("http://localhost")
1183                .profile_name("custom")
1184                .profile_files(
1185                    #[allow(deprecated)]
1186                    ProfileFiles::builder()
1187                        .with_file(
1188                            #[allow(deprecated)]
1189                            ProfileFileKind::Config,
1190                            "test_config",
1191                        )
1192                        .build(),
1193                )
1194                .load()
1195                .await;
1196            assert_eq!(Some("http://localhost"), config.endpoint_url());
1197        }
1198    }
1199}