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