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 profiles = conf.profile().await;
898 let service_config = EnvServiceConfig {
899 env: conf.env(),
900 env_config_sections: profiles.cloned().unwrap_or_default(),
901 };
902 let mut builder = SdkConfig::builder()
903 .region(region.clone())
904 .retry_config(retry_config)
905 .timeout_config(timeout_config)
906 .time_source(time_source)
907 .service_config(service_config);
908
909 // If an endpoint URL is set programmatically, then our work is done.
910 let endpoint_url = if self.endpoint_url.is_some() {
911 builder.insert_origin("endpoint_url", Origin::shared_config());
912 self.endpoint_url
913 } else {
914 // Otherwise, check to see if we should ignore EP URLs set in the environment.
915 let ignore_configured_endpoint_urls =
916 ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
917 .await
918 .unwrap_or_default();
919
920 if ignore_configured_endpoint_urls {
921 // If yes, log a trace and return `None`.
922 tracing::trace!(
923 "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
924 NOTE: Endpoint URLs set programmatically WILL still be respected"
925 );
926 None
927 } else {
928 // Otherwise, attempt to resolve one.
929 let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
930 builder.insert_origin("endpoint_url", origin);
931 v
932 }
933 };
934
935 let token_provider = match self.token_provider {
936 Some(provider) => {
937 builder.insert_origin("token_provider", Origin::shared_config());
938 Some(provider)
939 }
940 None => {
941 #[cfg(feature = "sso")]
942 {
943 let mut builder =
944 crate::default_provider::token::DefaultTokenChain::builder()
945 .configure(conf.clone());
946 builder.set_region(region);
947 Some(SharedTokenProvider::new(builder.build().await))
948 }
949 #[cfg(not(feature = "sso"))]
950 {
951 None
952 }
953 // Not setting `Origin` in this arm, and that's good for now as long as we know
954 // it's not programmatically set in the shared config.
955 // We can consider adding `Origin::Default` if needed.
956 }
957 };
958
959 builder.set_endpoint_url(endpoint_url);
960 builder.set_behavior_version(self.behavior_version);
961 builder.set_http_client(self.http_client);
962 builder.set_app_name(app_name);
963
964 let identity_cache = match self.identity_cache {
965 None => match self.behavior_version {
966 #[allow(deprecated)]
967 Some(bv) if bv.is_at_least(BehaviorVersion::v2024_03_28()) => {
968 Some(IdentityCache::lazy().build())
969 }
970 _ => None,
971 },
972 Some(user_cache) => Some(user_cache),
973 };
974
975 let request_checksum_calculation =
976 if let Some(request_checksum_calculation) = self.request_checksum_calculation {
977 Some(request_checksum_calculation)
978 } else {
979 checksums::request_checksum_calculation_provider(&conf).await
980 };
981
982 let response_checksum_validation =
983 if let Some(response_checksum_validation) = self.response_checksum_validation {
984 Some(response_checksum_validation)
985 } else {
986 checksums::response_checksum_validation_provider(&conf).await
987 };
988
989 let account_id_endpoint_mode =
990 if let Some(acccount_id_endpoint_mode) = self.account_id_endpoint_mode {
991 Some(acccount_id_endpoint_mode)
992 } else {
993 account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await
994 };
995
996 let auth_scheme_preference =
997 if let Some(auth_scheme_preference) = self.auth_scheme_preference {
998 builder.insert_origin("auth_scheme_preference", Origin::shared_config());
999 Some(auth_scheme_preference)
1000 } else {
1001 auth_scheme_preference::auth_scheme_preference_provider(&conf).await
1002 // Not setting `Origin` in this arm, and that's good for now as long as we know
1003 // it's not programmatically set in the shared config.
1004 };
1005
1006 builder.set_request_checksum_calculation(request_checksum_calculation);
1007 builder.set_response_checksum_validation(response_checksum_validation);
1008 builder.set_identity_cache(identity_cache);
1009 builder.set_credentials_provider(credentials_provider);
1010 builder.set_token_provider(token_provider);
1011 builder.set_sleep_impl(sleep_impl);
1012 builder.set_use_fips(use_fips);
1013 builder.set_use_dual_stack(use_dual_stack);
1014 builder.set_disable_request_compression(disable_request_compression);
1015 builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes);
1016 builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
1017 builder.set_account_id_endpoint_mode(account_id_endpoint_mode);
1018 builder.set_auth_scheme_preference(auth_scheme_preference);
1019 builder.build()
1020 }
1021 }
1022
1023 #[cfg(test)]
1024 impl ConfigLoader {
1025 pub(crate) fn env(mut self, env: Env) -> Self {
1026 self.env = Some(env);
1027 self
1028 }
1029
1030 pub(crate) fn fs(mut self, fs: Fs) -> Self {
1031 self.fs = Some(fs);
1032 self
1033 }
1034 }
1035
1036 #[cfg(test)]
1037 mod test {
1038 #[allow(deprecated)]
1039 use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
1040 use crate::test_case::{no_traffic_client, InstantSleep};
1041 use crate::BehaviorVersion;
1042 use crate::{defaults, ConfigLoader};
1043 use aws_credential_types::provider::ProvideCredentials;
1044 use aws_smithy_async::rt::sleep::TokioSleep;
1045 use aws_smithy_http_client::test_util::{infallible_client_fn, NeverClient};
1046 use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
1047 use aws_types::app_name::AppName;
1048 use aws_types::origin::Origin;
1049 use aws_types::os_shim_internal::{Env, Fs};
1050 use aws_types::sdk_config::{RequestChecksumCalculation, ResponseChecksumValidation};
1051 use std::sync::atomic::{AtomicUsize, Ordering};
1052 use std::sync::Arc;
1053
1054 #[tokio::test]
1055 async fn provider_config_used() {
1056 let (_guard, logs_rx) = capture_test_logs();
1057 let env = Env::from_slice(&[
1058 ("AWS_MAX_ATTEMPTS", "10"),
1059 ("AWS_REGION", "us-west-4"),
1060 ("AWS_ACCESS_KEY_ID", "akid"),
1061 ("AWS_SECRET_ACCESS_KEY", "secret"),
1062 ]);
1063 let fs =
1064 Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
1065 let loader = defaults(BehaviorVersion::latest())
1066 .sleep_impl(TokioSleep::new())
1067 .env(env)
1068 .fs(fs)
1069 .http_client(NeverClient::new())
1070 .profile_name("custom")
1071 .profile_files(
1072 #[allow(deprecated)]
1073 ProfileFiles::builder()
1074 .with_file(
1075 #[allow(deprecated)]
1076 ProfileFileKind::Config,
1077 "test_config",
1078 )
1079 .build(),
1080 )
1081 .load()
1082 .await;
1083 assert_eq!(10, loader.retry_config().unwrap().max_attempts());
1084 assert_eq!("us-west-4", loader.region().unwrap().as_ref());
1085 assert_eq!(
1086 "akid",
1087 loader
1088 .credentials_provider()
1089 .unwrap()
1090 .provide_credentials()
1091 .await
1092 .unwrap()
1093 .access_key_id(),
1094 );
1095 assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
1096
1097 let num_config_loader_logs = logs_rx.contents()
1098 .lines()
1099 // The logger uses fancy formatting, so we have to account for that.
1100 .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}"))
1101 .count();
1102
1103 match num_config_loader_logs {
1104 0 => panic!("no config file logs found!"),
1105 1 => (),
1106 more => panic!("the config file was parsed more than once! (parsed {more})",),
1107 };
1108 }
1109
1110 fn base_conf() -> ConfigLoader {
1111 defaults(BehaviorVersion::latest())
1112 .sleep_impl(InstantSleep)
1113 .http_client(no_traffic_client())
1114 }
1115
1116 #[tokio::test]
1117 async fn test_origin_programmatic() {
1118 let _ = tracing_subscriber::fmt::try_init();
1119 let loader = base_conf()
1120 .test_credentials()
1121 .profile_name("custom")
1122 .profile_files(
1123 #[allow(deprecated)]
1124 ProfileFiles::builder()
1125 .with_contents(
1126 #[allow(deprecated)]
1127 ProfileFileKind::Config,
1128 "[profile custom]\nendpoint_url = http://localhost:8989",
1129 )
1130 .build(),
1131 )
1132 .endpoint_url("http://localhost:1111")
1133 .load()
1134 .await;
1135 assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
1136 }
1137
1138 #[tokio::test]
1139 async fn test_origin_env() {
1140 let _ = tracing_subscriber::fmt::try_init();
1141 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
1142 let loader = base_conf()
1143 .test_credentials()
1144 .env(env)
1145 .profile_name("custom")
1146 .profile_files(
1147 #[allow(deprecated)]
1148 ProfileFiles::builder()
1149 .with_contents(
1150 #[allow(deprecated)]
1151 ProfileFileKind::Config,
1152 "[profile custom]\nendpoint_url = http://localhost:8989",
1153 )
1154 .build(),
1155 )
1156 .load()
1157 .await;
1158 assert_eq!(
1159 Origin::shared_environment_variable(),
1160 loader.get_origin("endpoint_url")
1161 );
1162 }
1163
1164 #[tokio::test]
1165 async fn test_origin_fs() {
1166 let _ = tracing_subscriber::fmt::try_init();
1167 let loader = base_conf()
1168 .test_credentials()
1169 .profile_name("custom")
1170 .profile_files(
1171 #[allow(deprecated)]
1172 ProfileFiles::builder()
1173 .with_contents(
1174 #[allow(deprecated)]
1175 ProfileFileKind::Config,
1176 "[profile custom]\nendpoint_url = http://localhost:8989",
1177 )
1178 .build(),
1179 )
1180 .load()
1181 .await;
1182 assert_eq!(
1183 Origin::shared_profile_file(),
1184 loader.get_origin("endpoint_url")
1185 );
1186 }
1187
1188 #[tokio::test]
1189 async fn load_use_fips() {
1190 let conf = base_conf().use_fips(true).load().await;
1191 assert_eq!(Some(true), conf.use_fips());
1192 }
1193
1194 #[tokio::test]
1195 async fn load_dual_stack() {
1196 let conf = base_conf().use_dual_stack(false).load().await;
1197 assert_eq!(Some(false), conf.use_dual_stack());
1198
1199 let conf = base_conf().load().await;
1200 assert_eq!(None, conf.use_dual_stack());
1201 }
1202
1203 #[tokio::test]
1204 async fn load_disable_request_compression() {
1205 let conf = base_conf().disable_request_compression(true).load().await;
1206 assert_eq!(Some(true), conf.disable_request_compression());
1207
1208 let conf = base_conf().load().await;
1209 assert_eq!(None, conf.disable_request_compression());
1210 }
1211
1212 #[tokio::test]
1213 async fn load_request_min_compression_size_bytes() {
1214 let conf = base_conf()
1215 .request_min_compression_size_bytes(99)
1216 .load()
1217 .await;
1218 assert_eq!(Some(99), conf.request_min_compression_size_bytes());
1219
1220 let conf = base_conf().load().await;
1221 assert_eq!(None, conf.request_min_compression_size_bytes());
1222 }
1223
1224 #[tokio::test]
1225 async fn app_name() {
1226 let app_name = AppName::new("my-app-name").unwrap();
1227 let conf = base_conf().app_name(app_name.clone()).load().await;
1228 assert_eq!(Some(&app_name), conf.app_name());
1229 }
1230
1231 #[tokio::test]
1232 async fn request_checksum_calculation() {
1233 let conf = base_conf()
1234 .request_checksum_calculation(RequestChecksumCalculation::WhenRequired)
1235 .load()
1236 .await;
1237 assert_eq!(
1238 Some(RequestChecksumCalculation::WhenRequired),
1239 conf.request_checksum_calculation()
1240 );
1241 }
1242
1243 #[tokio::test]
1244 async fn response_checksum_validation() {
1245 let conf = base_conf()
1246 .response_checksum_validation(ResponseChecksumValidation::WhenRequired)
1247 .load()
1248 .await;
1249 assert_eq!(
1250 Some(ResponseChecksumValidation::WhenRequired),
1251 conf.response_checksum_validation()
1252 );
1253 }
1254
1255 #[cfg(feature = "default-https-client")]
1256 #[tokio::test]
1257 async fn disable_default_credentials() {
1258 let config = defaults(BehaviorVersion::latest())
1259 .no_credentials()
1260 .load()
1261 .await;
1262 assert!(config.credentials_provider().is_none());
1263 }
1264
1265 #[cfg(feature = "default-https-client")]
1266 #[tokio::test]
1267 async fn identity_cache_defaulted() {
1268 let config = defaults(BehaviorVersion::latest()).load().await;
1269
1270 assert!(config.identity_cache().is_some());
1271 }
1272
1273 #[cfg(feature = "default-https-client")]
1274 #[allow(deprecated)]
1275 #[tokio::test]
1276 async fn identity_cache_old_behavior_version() {
1277 let config = defaults(BehaviorVersion::v2023_11_09()).load().await;
1278
1279 assert!(config.identity_cache().is_none());
1280 }
1281
1282 #[tokio::test]
1283 async fn connector_is_shared() {
1284 let num_requests = Arc::new(AtomicUsize::new(0));
1285 let movable = num_requests.clone();
1286 let http_client = infallible_client_fn(move |_req| {
1287 movable.fetch_add(1, Ordering::Relaxed);
1288 http::Response::new("ok!")
1289 });
1290 let config = defaults(BehaviorVersion::latest())
1291 .fs(Fs::from_slice(&[]))
1292 .env(Env::from_slice(&[]))
1293 .http_client(http_client.clone())
1294 .load()
1295 .await;
1296 config
1297 .credentials_provider()
1298 .unwrap()
1299 .provide_credentials()
1300 .await
1301 .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1302 let num_requests = num_requests.load(Ordering::Relaxed);
1303 assert!(num_requests > 0, "{}", num_requests);
1304 }
1305
1306 #[tokio::test]
1307 async fn endpoint_urls_may_be_ignored_from_env() {
1308 let fs = Fs::from_slice(&[(
1309 "test_config",
1310 "[profile custom]\nendpoint_url = http://profile",
1311 )]);
1312 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1313
1314 let conf = base_conf().use_dual_stack(false).load().await;
1315 assert_eq!(Some(false), conf.use_dual_stack());
1316
1317 let conf = base_conf().load().await;
1318 assert_eq!(None, conf.use_dual_stack());
1319
1320 // Check that we get nothing back because the env said we should ignore endpoints
1321 let config = base_conf()
1322 .fs(fs.clone())
1323 .env(env)
1324 .profile_name("custom")
1325 .profile_files(
1326 #[allow(deprecated)]
1327 ProfileFiles::builder()
1328 .with_file(
1329 #[allow(deprecated)]
1330 ProfileFileKind::Config,
1331 "test_config",
1332 )
1333 .build(),
1334 )
1335 .load()
1336 .await;
1337 assert_eq!(None, config.endpoint_url());
1338
1339 // Check that without the env, we DO get something back
1340 let config = base_conf()
1341 .fs(fs)
1342 .profile_name("custom")
1343 .profile_files(
1344 #[allow(deprecated)]
1345 ProfileFiles::builder()
1346 .with_file(
1347 #[allow(deprecated)]
1348 ProfileFileKind::Config,
1349 "test_config",
1350 )
1351 .build(),
1352 )
1353 .load()
1354 .await;
1355 assert_eq!(Some("http://profile"), config.endpoint_url());
1356 }
1357
1358 #[tokio::test]
1359 async fn endpoint_urls_may_be_ignored_from_profile() {
1360 let fs = Fs::from_slice(&[(
1361 "test_config",
1362 "[profile custom]\nignore_configured_endpoint_urls = true",
1363 )]);
1364 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1365
1366 // Check that we get nothing back because the profile said we should ignore endpoints
1367 let config = base_conf()
1368 .fs(fs)
1369 .env(env.clone())
1370 .profile_name("custom")
1371 .profile_files(
1372 #[allow(deprecated)]
1373 ProfileFiles::builder()
1374 .with_file(
1375 #[allow(deprecated)]
1376 ProfileFileKind::Config,
1377 "test_config",
1378 )
1379 .build(),
1380 )
1381 .load()
1382 .await;
1383 assert_eq!(None, config.endpoint_url());
1384
1385 // Check that without the profile, we DO get something back
1386 let config = base_conf().env(env).load().await;
1387 assert_eq!(Some("http://environment"), config.endpoint_url());
1388 }
1389
1390 #[tokio::test]
1391 async fn programmatic_endpoint_urls_may_not_be_ignored() {
1392 let fs = Fs::from_slice(&[(
1393 "test_config",
1394 "[profile custom]\nignore_configured_endpoint_urls = true",
1395 )]);
1396 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1397
1398 // Check that we get something back because we explicitly set the loader's endpoint URL
1399 let config = base_conf()
1400 .fs(fs)
1401 .env(env)
1402 .endpoint_url("http://localhost")
1403 .profile_name("custom")
1404 .profile_files(
1405 #[allow(deprecated)]
1406 ProfileFiles::builder()
1407 .with_file(
1408 #[allow(deprecated)]
1409 ProfileFileKind::Config,
1410 "test_config",
1411 )
1412 .build(),
1413 )
1414 .load()
1415 .await;
1416 assert_eq!(Some("http://localhost"), config.endpoint_url());
1417 }
1418 }
1419}