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