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