diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index d75cc4e8fb4..913d3e35239 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -87,3 +87,46 @@ message = "The constraint `@length` on non-streaming blob shapes is supported." references = ["smithy-rs#2131"] meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "server"} author = "82marbag" + +[[aws-sdk-rust]] +references = ["smithy-rs#2152"] +meta = { "breaking" = false, "tada" = false, "bug" = false } +author = "rcoh" +message = """Add support for overriding profile name and profile file location across all providers. Prior to this change, each provider needed to be updated individually. + +### Before +```rust +use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider}; +use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind}; + +let profile_files = ProfileFiles::builder() + .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file") + .build(); +let credentials_provider = ProfileFileCredentialsProvider::builder() + .profile_files(profile_files.clone()) + .build(); +let region_provider = ProfileFileRegionProvider::builder() + .profile_files(profile_files) + .build(); + +let sdk_config = aws_config::from_env() + .credentials_provider(credentials_provider) + .region(region_provider) + .load() + .await; +``` + +### After +```rust +use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider}; +use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind}; + +let profile_files = ProfileFiles::builder() + .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file") + .build(); +let sdk_config = aws_config::from_env() + .profile_files(profile_files) + .load() + .await; +/// ``` +""" diff --git a/aws/rust-runtime/aws-config/src/default_provider/app_name.rs b/aws/rust-runtime/aws-config/src/default_provider/app_name.rs index af8dcfa00e9..f6fce3d7305 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/app_name.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/app_name.rs @@ -52,8 +52,9 @@ impl Builder { #[cfg(test)] mod tests { use super::*; + use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::{no_traffic_connector, InstantSleep}; use aws_types::os_shim_internal::{Env, Fs}; #[tokio::test] @@ -76,6 +77,28 @@ mod tests { assert_eq!(Some(AppName::new("correct").unwrap()), app_name); } + // test that overriding profile_name on the root level is deprecated + #[tokio::test] + async fn profile_name_override() { + let fs = Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]); + let conf = crate::from_env() + .configure( + ProviderConfig::empty() + .with_fs(fs) + .with_sleep(InstantSleep) + .with_http_connector(no_traffic_connector()), + ) + .profile_name("custom") + .profile_files( + ProfileFiles::builder() + .with_file(ProfileFileKind::Config, "test_config") + .build(), + ) + .load() + .await; + assert_eq!(conf.app_name(), Some(&AppName::new("correct").unwrap())); + } + #[tokio::test] async fn load_from_profile() { let fs = Fs::from_slice(&[("test_config", "[default]\nsdk-ua-app-id = correct")]); diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 567cdaff172..cb041ef50d6 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -161,6 +161,7 @@ mod loader { use crate::connector::default_connector; use crate::default_provider::{app_name, credentials, region, retry_config, timeout_config}; use crate::meta::region::ProvideRegion; + use crate::profile::profile_file::ProfileFiles; use crate::provider_config::ProviderConfig; /// Load a cross-service [`SdkConfig`](aws_types::SdkConfig) from the environment @@ -181,6 +182,8 @@ mod loader { timeout_config: Option, provider_config: Option, http_connector: Option, + profile_name_override: Option, + profile_files_override: Option, } impl ConfigLoader { @@ -344,6 +347,73 @@ mod loader { self } + /// Provides the ability to programmatically override the profile files that get loaded by the SDK. + /// + /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in + /// `~/.aws/config` and `~/.aws/credentials` respectively. + /// + /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the + /// only requirement being that there is at least one of them. Custom file locations that are added + /// will produce errors if they don't exist, while the default config/credentials files paths are + /// allowed to not exist even if they're included. + /// + /// # Example: Using a custom profile file path + /// + /// ``` + /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider}; + /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind}; + /// + /// # async fn example() { + /// let profile_files = ProfileFiles::builder() + /// .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file") + /// .build(); + /// let sdk_config = aws_config::from_env() + /// .profile_files(profile_files) + /// .load() + /// .await; + /// # } + pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self { + self.profile_files_override = Some(profile_files); + self + } + + /// Override the profile name used by configuration providers + /// + /// Profile name is selected from an ordered list of sources: + /// 1. This override. + /// 2. The value of the `AWS_PROFILE` environment variable. + /// 3. `default` + /// + /// Each AWS profile has a name. For example, in the file below, the profiles are named + /// `dev`, `prod` and `staging`: + /// ```ini + /// [dev] + /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444 + /// + /// [staging] + /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444 + /// + /// [prod] + /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444 + /// ``` + /// + /// # Example: Using a custom profile name + /// + /// ``` + /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider}; + /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind}; + /// + /// # async fn example() { + /// let sdk_config = aws_config::from_env() + /// .profile_name("prod") + /// .load() + /// .await; + /// # } + pub fn profile_name(mut self, profile_name: impl Into) -> Self { + self.profile_name_override = Some(profile_name.into()); + self + } + /// Override the endpoint URL used for **all** AWS services. /// /// This method will override the endpoint URL used for **all** AWS services. This primarily @@ -372,16 +442,14 @@ mod loader { /// /// Update the `ProviderConfig` used for all nested loaders. This can be used to override /// the HTTPs connector used by providers or to stub in an in memory `Env` or `Fs` for testing. - /// This **does not set** the HTTP connector used when sending operations. To change that - /// connector, use [ConfigLoader::http_connector]. /// /// # Examples /// ```no_run /// # #[cfg(feature = "hyper-client")] /// # async fn create_config() { /// use aws_config::provider_config::ProviderConfig; - /// let custom_https_connector = hyper_rustls::HttpsConnectorBuilder::new(). - /// with_webpki_roots() + /// let custom_https_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .build(); @@ -404,7 +472,10 @@ mod loader { /// This means that if you provide a region provider that does not return a region, no region will /// be set in the resulting [`SdkConfig`](aws_types::SdkConfig) pub async fn load(self) -> SdkConfig { - let conf = self.provider_config.unwrap_or_default(); + let conf = self + .provider_config + .unwrap_or_default() + .with_profile_config(self.profile_files_override, self.profile_name_override); let region = if let Some(provider) = self.region { provider.region().await } else { diff --git a/aws/rust-runtime/aws-config/src/profile/profile_file.rs b/aws/rust-runtime/aws-config/src/profile/profile_file.rs index 76bdfb1fbae..ac7dee3764b 100644 --- a/aws/rust-runtime/aws-config/src/profile/profile_file.rs +++ b/aws/rust-runtime/aws-config/src/profile/profile_file.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; /// will produce errors if they don't exist, while the default config/credentials files paths are /// allowed to not exist even if they're included. /// -/// # Example: Using a custom profile file path for credentials and region +/// # Example: Using a custom profile file path /// /// ``` /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider}; @@ -28,16 +28,8 @@ use std::path::PathBuf; /// let profile_files = ProfileFiles::builder() /// .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file") /// .build(); -/// let credentials_provider = ProfileFileCredentialsProvider::builder() -/// .profile_files(profile_files.clone()) -/// .build(); -/// let region_provider = ProfileFileRegionProvider::builder() -/// .profile_files(profile_files) -/// .build(); -/// /// let sdk_config = aws_config::from_env() -/// .credentials_provider(credentials_provider) -/// .region(region_provider) +/// .profile_files(profile_files) /// .load() /// .await; /// # } diff --git a/aws/rust-runtime/aws-config/src/profile/retry_config.rs b/aws/rust-runtime/aws-config/src/profile/retry_config.rs index b266389bd6a..4a0ec2b3b33 100644 --- a/aws/rust-runtime/aws-config/src/profile/retry_config.rs +++ b/aws/rust-runtime/aws-config/src/profile/retry_config.rs @@ -84,9 +84,7 @@ impl ProfileFileRetryConfigProvider { /// /// To override the selected profile, set the `AWS_PROFILE` environment variable or use the [Builder]. pub fn new() -> Self { - Self { - provider_config: Default::default(), - } + Self::default() } /// [Builder] to construct a [ProfileFileRetryConfigProvider] diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index 01d7f93f3b9..43a4bdcebfc 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -42,7 +42,7 @@ pub struct ProviderConfig { connector: HttpConnector, sleep: Option>, region: Option, - profile: Arc>>, + parsed_profile: Arc>>, profile_files: ProfileFiles, profile_name_override: Option>, } @@ -73,7 +73,7 @@ impl Default for ProviderConfig { connector, sleep: default_async_sleep(), region: None, - profile: Default::default(), + parsed_profile: Default::default(), profile_files: ProfileFiles::default(), profile_name_override: None, } @@ -93,7 +93,7 @@ impl ProviderConfig { let fs = Fs::from_raw_map(HashMap::new()); let env = Env::from_slice(&[]); Self { - profile: Default::default(), + parsed_profile: Default::default(), profile_files: ProfileFiles::default(), env, fs, @@ -141,7 +141,7 @@ impl ProviderConfig { connector: HttpConnector::Prebuilt(None), sleep: None, region: None, - profile: Default::default(), + parsed_profile: Default::default(), profile_files: ProfileFiles::default(), profile_name_override: None, } @@ -203,7 +203,7 @@ impl ProviderConfig { pub(crate) async fn try_profile(&self) -> Result<&ProfileSet, &ProfileFileLoadError> { let parsed_profile = self - .profile + .parsed_profile .get_or_init(|| async { let profile = profile::load( &self.fs, @@ -231,22 +231,14 @@ impl ProviderConfig { self } - /// Override the profile files for the configuration - pub fn with_profile_files(self, profile_files: ProfileFiles) -> Self { - ProviderConfig { - profile: Default::default(), - profile_files, - ..self - } - } - pub(crate) fn with_profile_config( self, profile_files: Option, profile_name_override: Option, ) -> Self { ProviderConfig { - profile: Default::default(), + // clear out the profile since we need to reparse it + parsed_profile: Default::default(), profile_files: profile_files.unwrap_or(self.profile_files), profile_name_override: profile_name_override .map(Cow::Owned) @@ -270,7 +262,7 @@ impl ProviderConfig { #[doc(hidden)] pub fn with_fs(self, fs: Fs) -> Self { ProviderConfig { - profile: Default::default(), + parsed_profile: Default::default(), fs, ..self } @@ -279,7 +271,7 @@ impl ProviderConfig { #[doc(hidden)] pub fn with_env(self, env: Env) -> Self { ProviderConfig { - profile: Default::default(), + parsed_profile: Default::default(), env, ..self } diff --git a/aws/rust-runtime/aws-config/src/test_case.rs b/aws/rust-runtime/aws-config/src/test_case.rs index 7ef04507ef2..c8a5c132382 100644 --- a/aws/rust-runtime/aws-config/src/test_case.rs +++ b/aws/rust-runtime/aws-config/src/test_case.rs @@ -78,7 +78,7 @@ pub(crate) fn no_traffic_connector() -> DynConnector { } #[derive(Debug)] -struct InstantSleep; +pub(crate) struct InstantSleep; impl AsyncSleep for InstantSleep { fn sleep(&self, _duration: Duration) -> Sleep { Sleep::new(std::future::ready(()))