Create Profile File Provider for Region (#682)

* Create Profile File Provider for Region

As part of this work, I unified handling of provider configuration into `ProviderConfig`. This simplifies and removes boilerplate from the credential provider builders.

* Fix profile name bug, add test

* More cleanups around connection management

* Lots of cleanups around connector handling and crate features

* Update Changelog

* Remove unecessary sleep module

* Add builder convenience method

* CR feedback

* Update aws/rust-runtime/aws-config/src/default_provider.rs

Co-authored-by: John DiSanti <jdisanti@amazon.com>

Co-authored-by: John DiSanti <jdisanti@amazon.com>
This commit is contained in:
Russell Cohen 2021-09-02 09:54:51 -04:00 committed by GitHub
parent 0242158fe1
commit f649d559a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 707 additions and 287 deletions

View File

@ -18,7 +18,7 @@ Current Credential Provider Support:
- [x] Assume role from source profile
- [x] Static credentials source profile
- [x] WebTokenIdentity provider
- [ ] Region
- [x] Region
- [ ] IMDS
- [ ] ECS
@ -155,7 +155,7 @@ impl ProvideCredentials for CustomCreds {
**New this week**
- (When complete) Add Event Stream support (#653, #xyz)
- (When complete) Add profile file provider for region (#594, #xyz)
- Add profile file provider for region (#594, #682)
- Improve documentation on collection-aware builders (#664)
- Add support for Transcribe `StartStreamTranscription` and S3 `SelectObjectContent` operations (#667)
- Add support for shared configuration between multiple services (#673)

View File

@ -7,11 +7,12 @@ exclude = ["test-data/*"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default-provider = ["profile", "imds", "meta", "sts"]
profile = ["sts", "web-identity-token"]
default-provider = ["profile", "imds", "meta", "sts", "environment"]
profile = ["sts", "web-identity-token", "meta", "environment"]
# note: IMDS currently unsupported
imds = []
meta = ["tokio/sync"]
environment = ["meta"]
sts = ["aws-sdk-sts", "aws-hyper"]
web-identity-token = ["sts"]
sso = []

View File

@ -9,13 +9,76 @@
pub mod region {
use crate::environment::region::EnvironmentVariableRegionProvider;
use crate::meta::region::ProvideRegion;
use crate::meta::region::{ProvideRegion, RegionProviderChain};
use crate::profile;
use crate::provider_config::ProviderConfig;
use aws_types::region::Region;
/// Default Region Provider chain
///
/// This provider will load region from environment variables.
/// This provider will check the following sources in order:
/// 1. [Environment variables](EnvironmentVariableRegionProvider)
/// 2. [Profile file](crate::profile::region::ProfileFileRegionProvider)
pub fn default_provider() -> impl ProvideRegion {
EnvironmentVariableRegionProvider::new()
Builder::default().build()
}
/// Default region provider chain
#[derive(Debug)]
pub struct DefaultRegionChain(RegionProviderChain);
impl DefaultRegionChain {
/// Load a region from this chain
pub async fn region(&self) -> Option<Region> {
self.0.region().await
}
/// Builder for [`DefaultRegionChain`]
pub fn builder() -> Builder {
Builder::default()
}
}
/// Builder for [DefaultRegionChain]
#[derive(Default)]
pub struct Builder {
env_provider: EnvironmentVariableRegionProvider,
profile_file: profile::region::Builder,
}
impl Builder {
#[doc(hidden)]
/// Configure the default chain
///
/// Exposed for overriding the environment when unit-testing providers
pub fn configure(mut self, configuration: &ProviderConfig) -> Self {
self.env_provider =
EnvironmentVariableRegionProvider::new_with_env(configuration.env());
self.profile_file = self.profile_file.configure(configuration);
self
}
/// Override the profile name used by this provider
pub fn profile_name(mut self, name: &str) -> Self {
self.profile_file = self.profile_file.profile_name(name);
self
}
/// Build a [DefaultRegionChain]
pub fn build(self) -> DefaultRegionChain {
DefaultRegionChain(
RegionProviderChain::first_try(self.env_provider)
.or_else(self.profile_file.build()),
)
}
}
impl ProvideRegion for DefaultRegionChain {
fn region(&self) -> crate::meta::region::future::ProvideRegion {
ProvideRegion::region(&self.0)
}
}
}
@ -23,11 +86,11 @@ pub mod region {
pub mod credentials {
use crate::environment::credentials::EnvironmentVariableCredentialsProvider;
use crate::meta::credentials::{CredentialsProviderChain, LazyCachingCredentialsProvider};
use crate::meta::region::ProvideRegion;
use aws_types::credentials::{future, ProvideCredentials};
use aws_types::os_shim_internal::{Env, Fs};
use aws_types::region::Region;
use smithy_async::rt::sleep::AsyncSleep;
use smithy_client::erase::DynConnector;
use crate::provider_config::ProviderConfig;
use std::borrow::Cow;
#[cfg(any(feature = "rustls", feature = "native-tls"))]
@ -35,11 +98,7 @@ pub mod credentials {
///
/// The region from the default region provider will be used
pub async fn default_provider() -> impl ProvideCredentials {
use crate::meta::region::ProvideRegion;
let region = super::region::default_provider().region().await;
let mut builder = DefaultCredentialsChain::builder();
builder.set_region(region);
builder.build()
DefaultCredentialsChain::builder().build().await
}
/// Default AWS Credential Provider Chain
@ -52,7 +111,7 @@ pub mod credentials {
///
/// More providers are a work in progress.
///
/// ## Example:
/// # Examples
/// Create a default chain with a custom region:
/// ```rust
/// use aws_types::region::Region;
@ -67,6 +126,14 @@ pub mod credentials {
/// use aws_config::default_provider::credentials::DefaultCredentialsChain;
/// let credentials_provider = DefaultCredentialsChain::builder().build();
/// ```
///
/// Create a default chain that uses a different profile:
/// ```rust
/// use aws_config::default_provider::credentials::DefaultCredentialsChain;
/// let credentials_provider = DefaultCredentialsChain::builder()
/// .profile_name("otherprofile")
/// .build();
/// ```
#[derive(Debug)]
pub struct DefaultCredentialsChain(LazyCachingCredentialsProvider);
@ -92,14 +159,16 @@ pub mod credentials {
profile_file_builder: crate::profile::credentials::Builder,
web_identity_builder: crate::web_identity_token::Builder,
credential_cache: crate::meta::credentials::lazy_caching::Builder,
env: Option<Env>,
region_override: Option<Box<dyn ProvideRegion>>,
region_chain: crate::default_provider::region::Builder,
conf: Option<ProviderConfig>,
}
impl Builder {
/// Sets the region used when making requests to AWS services
///
/// When unset, the default region resolver chain will be used.
pub fn region(mut self, region: Region) -> Self {
pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
self.set_region(Some(region));
self
}
@ -107,30 +176,8 @@ pub mod credentials {
/// Sets the region used when making requests to AWS services
///
/// When unset, the default region resolver chain will be used.
pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
self.profile_file_builder.set_region(region.clone());
self.web_identity_builder.set_region(region);
self
}
/// Override the HTTPS connector used for this provider
///
/// If a connector other than Hyper is used or if the Tokio/Hyper features have been disabled
/// this method MUST be used to specify a custom connector.
pub fn connector(mut self, connector: DynConnector) -> Self {
self.profile_file_builder
.set_connector(Some(connector.clone()));
self.web_identity_builder.set_connector(Some(connector));
self
}
/// Override the sleep implementation used for this provider
///
/// By default, Tokio will be used to support async sleep during credentials for timeouts
/// and reloading credentials. If the tokio default feature has been disabled, a custom
/// sleep implementation must be provided.
pub fn sleep(mut self, sleep: impl AsyncSleep + 'static) -> Self {
self.credential_cache = self.credential_cache.sleep(sleep);
pub fn set_region(&mut self, region: Option<impl ProvideRegion + 'static>) -> &mut Self {
self.region_override = region.map(|provider| Box::new(provider) as _);
self
}
@ -158,24 +205,19 @@ pub mod credentials {
self
}
#[doc(hidden)]
/// Override the filesystem used for this provider
/// Override the profile name used by this provider
///
/// This method exists to test credential providers
pub fn fs(mut self, fs: Fs) -> Self {
self.profile_file_builder.set_fs(fs.clone());
self.web_identity_builder.set_fs(fs);
/// When unset, the value of the `AWS_PROFILE` environment variable will be used.
pub fn profile_name(mut self, name: &str) -> Self {
self.profile_file_builder = self.profile_file_builder.profile_name(name);
self.region_chain = self.region_chain.profile_name(name);
self
}
#[doc(hidden)]
/// Override the environment used for this provider
///
/// This method exists to test credential providers
pub fn env(mut self, env: Env) -> Self {
self.env = Some(env.clone());
self.profile_file_builder.set_env(env.clone());
self.web_identity_builder.set_env(env);
/// Override the configuration used for this provider
pub fn configure(mut self, config: ProviderConfig) -> Self {
self.region_chain = self.region_chain.configure(&config);
self.conf = Some(config);
self
}
@ -184,15 +226,21 @@ pub mod credentials {
/// ## Panics
/// This function will panic if no connector has been set and neither `rustls` and `native-tls`
/// features have both been disabled.
pub fn build(self) -> DefaultCredentialsChain {
let profile_provider = self.profile_file_builder.build();
let env_provider =
EnvironmentVariableCredentialsProvider::new_with_env(self.env.unwrap_or_default());
let web_identity_token_provider = self.web_identity_builder.build();
pub async fn build(self) -> DefaultCredentialsChain {
let region = match self.region_override {
Some(provider) => provider.region().await,
None => self.region_chain.build().region().await,
};
let conf = self.conf.unwrap_or_default().with_region(region);
let profile_provider = self.profile_file_builder.configure(&conf).build();
let env_provider = EnvironmentVariableCredentialsProvider::new_with_env(conf.env());
let web_identity_token_provider = self.web_identity_builder.configure(&conf).build();
let provider_chain = CredentialsProviderChain::first_try("Environment", env_provider)
.or_else("Profile", profile_provider)
.or_else("WebIdentityToken", web_identity_token_provider);
let cached_provider = self.credential_cache.load(provider_chain);
let cached_provider = self.credential_cache.configure(&conf).load(provider_chain);
DefaultCredentialsChain(cached_provider.build())
}
}
@ -210,21 +258,20 @@ pub mod credentials {
stringify!($name)
))
.unwrap()
.execute(|fs, env, conn| {
.execute(|conf| async {
crate::default_provider::credentials::Builder::default()
.env(env)
.fs(fs)
.region(Region::from_static("us-east-1"))
.connector(conn)
.configure(conf)
.build()
.await
})
.await
}
};
}
use aws_sdk_sts::Region;
use crate::default_provider::credentials::DefaultCredentialsChain;
use crate::test_case::TestEnvironment;
use aws_types::credentials::ProvideCredentials;
use tracing_test::traced_test;
make_test!(prefer_environment);
@ -236,6 +283,25 @@ pub mod credentials {
make_test!(web_identity_token_profile);
make_test!(profile_overrides_web_identity);
#[tokio::test]
async fn profile_name_override() {
let (_, conf) =
TestEnvironment::from_dir("./test-data/default-provider-chain/profile_static_keys")
.unwrap()
.provider_config()
.await;
let provider = DefaultCredentialsChain::builder()
.profile_name("secondary")
.configure(conf)
.build()
.await;
let creds = provider
.provide_credentials()
.await
.expect("creds should load");
assert_eq!(creds.access_key_id(), "correct_key_secondary");
}
/// Helper that uses `execute_and_update` instead of execute
///
/// If you run this, it will add another HTTP traffic log which re-records the request
@ -247,13 +313,8 @@ pub mod credentials {
"./test-data/default-provider-chain/web_identity_token_source_profile",
))
.unwrap()
.execute_and_update(|fs, env, conn| {
super::Builder::default()
.env(env)
.fs(fs)
.region(Region::from_static("us-east-1"))
.connector(conn)
.build()
.execute_and_update(|conf| async {
super::Builder::default().configure(conf).build().await
})
.await
}

View File

@ -42,6 +42,7 @@
#[cfg(feature = "default-provider")]
pub mod default_provider;
#[cfg(feature = "environment")]
/// Providers that load configuration from environment variables
pub mod environment;
@ -61,6 +62,8 @@ mod test_case;
#[cfg(feature = "web-identity-token")]
pub mod web_identity_token;
pub mod provider_config;
/// Create an environment loader for AWS Configuration
///
/// ## Example
@ -87,6 +90,7 @@ pub async fn load_from_env() -> aws_types::config::Config {
/// Load default sources for all configuration with override support
pub use loader::ConfigLoader;
#[cfg(feature = "default-provider")]
mod loader {
use crate::default_provider::{credentials, region};
use crate::meta::region::ProvideRegion;
@ -161,7 +165,7 @@ mod loader {
} else {
let mut builder = credentials::DefaultCredentialsChain::builder();
builder.set_region(region.clone());
SharedCredentialsProvider::new(builder.build())
SharedCredentialsProvider::new(builder.build().await)
};
Config::builder()
.region(region)
@ -183,22 +187,24 @@ mod connector {
use smithy_client::erase::DynConnector;
pub fn must_have_connector() -> DynConnector {
default_connector().expect("A connector was not available. Either set a custom connector or enable the `rustls` and `native-tls` crate features.")
// unused when all crate features are disabled
#[allow(dead_code)]
pub fn expect_connector(connector: Option<DynConnector>) -> DynConnector {
connector.expect("A connector was not available. Either set a custom connector or enable the `rustls` and `native-tls` crate features.")
}
#[cfg(feature = "rustls")]
fn default_connector() -> Option<DynConnector> {
pub fn default_connector() -> Option<DynConnector> {
Some(DynConnector::new(smithy_client::conns::https()))
}
#[cfg(all(not(feature = "rustls"), feature = "native-tls"))]
fn default_connector() -> Option<DynConnector> {
pub fn default_connector() -> Option<DynConnector> {
Some(DynConnector::new(smithy_client::conns::native_tls()))
}
#[cfg(not(any(feature = "rustls", feature = "native-tls")))]
fn default_connector() -> Option<DynConnector> {
pub fn default_connector() -> Option<DynConnector> {
None
}
}

View File

@ -53,8 +53,9 @@ impl CredentialsProviderChain {
self
}
#[cfg(feature = "default-provider")]
/// Add a fallback to the default provider chain
#[cfg(feature = "default-provider")]
#[cfg(any(feature = "rustls", feature = "native-tls"))]
pub async fn or_default_provider(self) -> Self {
self.or_else(
"DefaultProviderChain",
@ -62,8 +63,9 @@ impl CredentialsProviderChain {
)
}
#[cfg(feature = "default-provider")]
/// Creates a credential provider chain that starts with the default provider
#[cfg(feature = "default-provider")]
#[cfg(any(feature = "rustls", feature = "native-tls"))]
pub async fn default_provider() -> Self {
Self::first_try(
"DefaultProviderChain",

View File

@ -31,7 +31,7 @@ const DEFAULT_BUFFER_TIME: Duration = Duration::from_secs(10);
#[derive(Debug)]
pub struct LazyCachingCredentialsProvider {
time: Box<dyn TimeSource>,
sleeper: Box<dyn AsyncSleep>,
sleeper: Arc<dyn AsyncSleep>,
cache: Cache,
loader: Arc<dyn ProvideCredentials>,
load_timeout: Duration,
@ -41,7 +41,7 @@ pub struct LazyCachingCredentialsProvider {
impl LazyCachingCredentialsProvider {
fn new(
time: impl TimeSource,
sleeper: Box<dyn AsyncSleep>,
sleeper: Arc<dyn AsyncSleep>,
loader: Arc<dyn ProvideCredentials>,
load_timeout: Duration,
default_credential_expiration: Duration,
@ -121,6 +121,7 @@ mod builder {
DEFAULT_LOAD_TIMEOUT,
};
use crate::meta::credentials::lazy_caching::time::SystemTimeSource;
use crate::provider_config::ProviderConfig;
/// Builder for constructing a [`LazyCachingCredentialsProvider`].
///
@ -140,7 +141,7 @@ mod builder {
/// ```
#[derive(Default)]
pub struct Builder {
sleep: Option<Box<dyn AsyncSleep>>,
sleep: Option<Arc<dyn AsyncSleep>>,
load: Option<Arc<dyn ProvideCredentials>>,
load_timeout: Option<Duration>,
buffer_time: Option<Duration>,
@ -153,6 +154,12 @@ mod builder {
Default::default()
}
/// Override configuration for the [Builder]
pub fn configure(mut self, config: &ProviderConfig) -> Self {
self.sleep = config.sleep();
self
}
/// An implementation of [`ProvideCredentials`] that will be used to load
/// the cached credentials once they're expired.
pub fn load(mut self, loader: impl ProvideCredentials + 'static) -> Self {
@ -165,7 +172,7 @@ mod builder {
/// If using Tokio as the async runtime, this should be set to an instance of
/// [`TokioSleep`](smithy_async::rt::sleep::TokioSleep).
pub fn sleep(mut self, sleep: impl AsyncSleep + 'static) -> Self {
self.sleep = Some(Box::new(sleep));
self.sleep = Some(Arc::new(sleep));
self
}
@ -270,7 +277,7 @@ mod tests {
let load_list = Arc::new(Mutex::new(load_list));
LazyCachingCredentialsProvider::new(
time,
Box::new(TokioSleep::new()),
Arc::new(TokioSleep::new()),
Arc::new(provide_credentials_fn(move || {
let list = load_list.clone();
async move {
@ -311,7 +318,7 @@ mod tests {
}));
let provider = LazyCachingCredentialsProvider::new(
time,
Box::new(TokioSleep::new()),
Arc::new(TokioSleep::new()),
loader,
DEFAULT_LOAD_TIMEOUT,
DEFAULT_CREDENTIAL_EXPIRATION,
@ -422,7 +429,7 @@ mod tests {
let time = TestTime::new(epoch_secs(100));
let provider = LazyCachingCredentialsProvider::new(
time,
Box::new(TokioSleep::new()),
Arc::new(TokioSleep::new()),
Arc::new(provide_credentials_fn(|| async {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(credentials(1000))

View File

@ -32,11 +32,12 @@ use aws_types::os_shim_internal::{Env, Fs};
use aws_types::region::Region;
use tracing::Instrument;
use crate::connector::must_have_connector;
use crate::connector::expect_connector;
use crate::meta::region::ProvideRegion;
use crate::profile::credentials::exec::named::NamedProviderFactory;
use crate::profile::credentials::exec::{ClientConfiguration, ProviderChain};
use crate::profile::parser::ProfileParseError;
use crate::provider_config::ProviderConfig;
use smithy_client::erase::DynConnector;
mod exec;
@ -103,9 +104,11 @@ impl ProvideCredentials for ProfileFileCredentialsProvider {
/// future::ProvideCredentials::new(self.load_credentials())
/// }
/// }
/// # if cfg!(any(feature = "rustls", feature = "native-tls")) {
/// let provider = ProfileFileCredentialsProvider::builder()
/// .with_custom_provider("Custom", MyCustomProvider)
/// .build();
/// }
/// ```
///
/// ### Assume role credentials from a source profile
@ -128,6 +131,7 @@ pub struct ProfileFileCredentialsProvider {
env: Env,
region: Option<Region>,
connector: DynConnector,
profile_override: Option<String>,
}
impl ProfileFileCredentialsProvider {
@ -148,6 +152,7 @@ impl ProfileFileCredentialsProvider {
&self.region,
&self.connector,
&self.factory,
self.profile_override.as_deref(),
)
.await;
let inner_provider = profile.map_err(|err| match err {
@ -277,62 +282,29 @@ impl Error for ProfileFileError {
}
}
/// Builder for [`ProfileFileCredentialsProvider`](ProfileFileCredentialsProvider)
/// Builder for [`ProfileFileCredentialsProvider`]
#[derive(Default)]
pub struct Builder {
fs: Fs,
env: Env,
region: Option<Region>,
connector: Option<DynConnector>,
provider_config: Option<ProviderConfig>,
profile_override: Option<String>,
custom_providers: HashMap<Cow<'static, str>, Arc<dyn ProvideCredentials>>,
}
impl Builder {
#[doc(hidden)]
pub fn fs(mut self, fs: Fs) -> Self {
self.fs = fs;
self
}
#[doc(hidden)]
pub fn set_fs(&mut self, fs: Fs) -> &mut Self {
self.fs = fs;
self
}
#[doc(hidden)]
pub fn env(mut self, env: Env) -> Self {
self.env = env;
self
}
#[doc(hidden)]
pub fn set_env(&mut self, env: Env) -> &mut Self {
self.env = env;
self
}
/// Sets the HTTPS connector used for requests to AWS
pub fn connector(mut self, connector: DynConnector) -> Self {
self.connector = Some(connector);
self
}
/// Sets the HTTPS connector used for requests to AWS
pub fn set_connector(&mut self, connector: Option<DynConnector>) -> &mut Self {
self.connector = connector;
self
}
/// Sets the region used for requests to AWS
pub fn region(mut self, region: Region) -> Self {
self.region = Some(region);
self
}
/// Sets the region used for requests to AWS
pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
self.region = region;
/// Override the configuration for the [`ProfileFileCredentialsProvider`]
///
/// # Example
/// ```rust
/// # async fn test() {
/// use aws_config::profile::ProfileFileCredentialsProvider;
/// use aws_config::provider_config::ProviderConfig;
/// let provider = ProfileFileCredentialsProvider::builder()
/// .configure(&ProviderConfig::with_default_region().await)
/// .build();
/// # }
/// ```
pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
self.provider_config = Some(provider_config.clone());
self
}
@ -356,9 +328,12 @@ impl Builder {
/// future::ProvideCredentials::new(self.load_credentials())
/// }
/// }
///
/// # if cfg!(any(feature = "rustls", feature = "native-tls")) {
/// let provider = ProfileFileCredentialsProvider::builder()
/// .with_custom_provider("Custom", MyCustomProvider)
/// .build();
/// # }
/// ```
pub fn with_custom_provider(
mut self,
@ -370,35 +345,41 @@ impl Builder {
self
}
/// Builds a [`ProfileFileCredentialsProvider`](ProfileFileCredentialsProvider)
/// Override the profile name used by the [`ProfileFileCredentialsProvider`]
pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
self.profile_override = Some(profile_name.into());
self
}
/// Builds a [`ProfileFileCredentialsProvider`]
pub fn build(self) -> ProfileFileCredentialsProvider {
let build_span = tracing::info_span!("build_profile_provider");
let _enter = build_span.enter();
let env = self.env.clone();
let fs = self.fs;
let conf = self.provider_config.unwrap_or_default();
let mut named_providers = self.custom_providers.clone();
named_providers
.entry("Environment".into())
.or_insert_with(|| {
Arc::new(crate::environment::credentials::EnvironmentVariableCredentialsProvider::new_with_env(
env.clone(),
conf.env(),
))
});
// TODO: ECS, IMDS, and other named providers
let factory = exec::named::NamedProviderFactory::new(named_providers);
let connector = self.connector.clone().unwrap_or_else(must_have_connector);
let connector = expect_connector(conf.connector().cloned());
let core_client = aws_hyper::Client::new(connector.clone());
ProfileFileCredentialsProvider {
factory,
client_config: ClientConfiguration {
core_client,
region: self.region.clone(),
region: conf.region(),
},
fs,
env,
region: self.region.clone(),
fs: conf.fs(),
env: conf.env(),
region: conf.region(),
connector,
profile_override: self.profile_override,
}
}
}
@ -409,12 +390,13 @@ async fn build_provider_chain(
region: &dyn ProvideRegion,
connector: &DynConnector,
factory: &NamedProviderFactory,
profile_override: Option<&str>,
) -> Result<ProviderChain, ProfileFileError> {
let profile_set = super::parser::load(&fs, &env).await.map_err(|err| {
tracing::warn!(err = %err, "failed to parse profile");
ProfileFileError::CouldNotParseProfile(err)
})?;
let repr = repr::resolve_chain(&profile_set)?;
let repr = repr::resolve_chain(&profile_set, profile_override)?;
tracing::info!(chain = ?repr, "constructed abstract provider from config file");
exec::ProviderChain::from_repr(fs.clone(), connector, region.region().await, repr, &factory)
}
@ -425,7 +407,6 @@ mod test {
use crate::profile::credentials::Builder;
use crate::test_case::TestEnvironment;
use aws_types::region::Region;
macro_rules! make_test {
($name: ident) => {
@ -437,14 +418,7 @@ mod test {
stringify!($name)
))
.unwrap()
.execute(|fs, env, conn| {
Builder::default()
.env(env)
.fs(fs)
.region(Region::from_static("us-east-1"))
.connector(conn)
.build()
})
.execute(|conf| async move { Builder::default().configure(&conf).build() })
.await
}
};
@ -454,19 +428,5 @@ mod test {
make_test!(empty_config);
make_test!(retry_on_error);
make_test!(invalid_config);
#[tokio::test]
async fn region_override() {
TestEnvironment::from_dir("./test-data/profile-provider/region_override")
.unwrap()
.execute(|fs, env, conn| {
Builder::default()
.env(env)
.fs(fs)
.region(Region::from_static("us-east-2"))
.connector(conn)
.build()
})
.await
}
make_test!(region_override);
}

View File

@ -12,6 +12,7 @@ use aws_types::region::Region;
use super::repr::{self, BaseProvider};
use crate::profile::credentials::ProfileFileError;
use crate::provider_config::ProviderConfig;
use crate::sts;
use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentialsProvider};
use aws_types::credentials::{self, CredentialsError, ProvideCredentials};
@ -109,6 +110,10 @@ impl ProviderChain {
web_identity_token_file,
session_name,
} => {
let conf = ProviderConfig::empty()
.with_connector(connector.clone())
.with_fs(fs)
.with_region(region);
let provider = WebIdentityTokenCredentialsProvider::builder()
.static_configuration(StaticConfiguration {
web_identity_token_file: web_identity_token_file.into(),
@ -117,9 +122,7 @@ impl ProviderChain {
|| sts::util::default_session_name("web-identity-token-profile"),
),
})
.fs(fs)
.connector(connector.clone())
.region(region)
.configure(&conf)
.build();
Arc::new(provider)
}
@ -169,21 +172,16 @@ mod test {
use crate::profile::credentials::exec::named::NamedProviderFactory;
use crate::profile::credentials::exec::ProviderChain;
use crate::profile::credentials::repr::{BaseProvider, ProfileChain};
use crate::test_case::no_traffic_connector;
use aws_sdk_sts::Region;
use smithy_client::dvr;
use smithy_client::erase::DynConnector;
use std::collections::HashMap;
fn stub_connector() -> DynConnector {
DynConnector::new(dvr::ReplayingConnection::new(vec![]))
}
#[test]
fn error_on_unknown_provider() {
let factory = NamedProviderFactory::new(HashMap::new());
let chain = ProviderChain::from_repr(
Default::default(),
&stub_connector(),
&no_traffic_connector(),
Some(Region::new("us-east-1")),
ProfileChain {
base: BaseProvider::NamedSource("floozle"),

View File

@ -101,11 +101,15 @@ pub struct RoleArn<'a> {
}
/// Resolve a ProfileChain from a ProfileSet or return an error
pub fn resolve_chain(profile_set: &ProfileSet) -> Result<ProfileChain, ProfileFileError> {
pub fn resolve_chain<'a>(
profile_set: &'a ProfileSet,
profile_override: Option<&str>,
) -> Result<ProfileChain<'a>, ProfileFileError> {
if profile_set.is_empty() {
return Err(ProfileFileError::NoProfilesDefined);
}
let mut source_profile_name = profile_set.selected_profile();
let mut source_profile_name =
profile_override.unwrap_or_else(|| profile_set.selected_profile());
let mut visited_profiles = vec![];
let mut chain = vec![];
let base = loop {
@ -323,7 +327,7 @@ mod tests {
fn check(test_case: TestCase) {
let source = ProfileSet::new(test_case.input.profile, test_case.input.selected_profile);
let actual = resolve_chain(&source);
let actual = resolve_chain(&source, None);
let expected = test_case.output;
match (expected, actual) {
(TestOutput::Error(s), Err(e)) => assert!(

View File

@ -12,4 +12,7 @@ mod parser;
pub use parser::{load, Profile, ProfileSet, Property};
pub mod credentials;
pub mod region;
pub use credentials::ProfileFileCredentialsProvider;
pub use region::ProfileFileRegionProvider;

View File

@ -0,0 +1,174 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
//! Load a region from an AWS profile
use crate::meta::region::{future, ProvideRegion};
use crate::provider_config::ProviderConfig;
use aws_types::os_shim_internal::{Env, Fs};
use aws_types::region::Region;
/// Load a region from a profile file
///
/// This provider will attempt to load AWS shared configuration, then read the `region` property
/// from the active profile.
///
/// Example:
///
/// ```ini
/// # `~/.aws/config
/// [default]
/// region = us-west-2
/// ```
///
/// This provider is part of the [default provider chain](crate::default_provider::region).
#[derive(Debug, Default)]
pub struct ProfileFileRegionProvider {
fs: Fs,
env: Env,
profile_override: Option<String>,
}
/// Builder for [ProfileFileRegionProvider]
#[derive(Default)]
pub struct Builder {
config: Option<ProviderConfig>,
profile_override: Option<String>,
}
impl Builder {
/// Override the configuration for this provider
pub fn configure(mut self, config: &ProviderConfig) -> Self {
self.config = Some(config.clone());
self
}
/// Override the profile name used by the [ProfileFileRegionProvider]
pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
self.profile_override = Some(profile_name.into());
self
}
/// Build a [ProfileFileRegionProvider] from this builder
pub fn build(self) -> ProfileFileRegionProvider {
let conf = self.config.unwrap_or_default();
ProfileFileRegionProvider {
env: conf.env(),
fs: conf.fs(),
profile_override: self.profile_override,
}
}
}
impl ProfileFileRegionProvider {
/// Create a new [ProfileFileRegionProvider]
///
/// To override the selected profile, set the `AWS_PROFILE` environment variable or use the [Builder].
pub fn new() -> Self {
Self {
fs: Fs::real(),
env: Env::real(),
profile_override: None,
}
}
/// [Builder] to construct a [ProfileFileRegionProvider]
pub fn builder() -> Builder {
Builder::default()
}
async fn region(&self) -> Option<Region> {
let profile = super::parser::load(&self.fs, &self.env)
.await
.map_err(|err| tracing::warn!(err = %err, "failed to parse profile"))
.ok()?;
let selected_profile = self
.profile_override
.as_deref()
.unwrap_or_else(|| profile.selected_profile());
let selected_profile = profile.get_profile(selected_profile)?;
selected_profile
.get("region")
.map(|region| Region::new(region.to_owned()))
}
}
impl ProvideRegion for ProfileFileRegionProvider {
fn region(&self) -> future::ProvideRegion {
future::ProvideRegion::new(self.region())
}
}
#[cfg(test)]
mod test {
use crate::profile::ProfileFileRegionProvider;
use crate::provider_config::ProviderConfig;
use crate::test_case::no_traffic_connector;
use aws_sdk_sts::Region;
use aws_types::os_shim_internal::{Env, Fs};
use futures_util::FutureExt;
use tracing_test::traced_test;
fn provider_config(dir_name: &str) -> ProviderConfig {
let fs = Fs::from_test_dir(format!("test-data/profile-provider/{}/fs", dir_name), "/");
let env = Env::from_slice(&[("HOME", "/home")]);
ProviderConfig::empty()
.with_fs(fs)
.with_env(env)
.with_connector(no_traffic_connector())
}
#[traced_test]
#[test]
fn load_region() {
let provider = ProfileFileRegionProvider::builder()
.configure(&provider_config("region_override"))
.build();
assert_eq!(
provider.region().now_or_never().unwrap(),
Some(Region::from_static("us-east-1"))
);
}
#[test]
fn load_region_env_profile_override() {
let conf = provider_config("region_override").with_env(Env::from_slice(&[
("HOME", "/home"),
("AWS_PROFILE", "base"),
]));
let provider = ProfileFileRegionProvider::builder()
.configure(&conf)
.build();
assert_eq!(
provider.region().now_or_never().unwrap(),
Some(Region::from_static("us-east-1"))
);
}
#[test]
fn load_region_nonexistent_profile() {
let conf = provider_config("region_override").with_env(Env::from_slice(&[
("HOME", "/home"),
("AWS_PROFILE", "doesnotexist"),
]));
let provider = ProfileFileRegionProvider::builder()
.configure(&conf)
.build();
assert_eq!(provider.region().now_or_never().unwrap(), None);
}
#[test]
fn load_region_explicit_override() {
let conf = provider_config("region_override");
let provider = ProfileFileRegionProvider::builder()
.configure(&conf)
.profile_name("base")
.build();
assert_eq!(
provider.region().now_or_never().unwrap(),
Some(Region::from_static("us-east-1"))
);
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
//! Configuration Options for Credential Providers
use crate::connector::default_connector;
use crate::default_provider::region::DefaultRegionChain;
use aws_types::os_shim_internal::{Env, Fs};
use aws_types::region::Region;
use smithy_async::rt::sleep::{default_async_sleep, AsyncSleep};
use smithy_client::erase::DynConnector;
use std::sync::Arc;
/// Configuration options for Credential Providers
///
/// Most credential providers builders offer a `configure` method which applies general provider configuration
/// options.
///
/// To use a region from the default region provider chain use [`ProviderConfig::with_default_region`].
/// Otherwise, use [`ProviderConfig::without_region`]. Note that some credentials providers require a region
/// to be explicitly set.
#[derive(Clone)]
pub struct ProviderConfig {
env: Env,
fs: Fs,
connector: Option<DynConnector>,
sleep: Option<Arc<dyn AsyncSleep>>,
region: Option<Region>,
}
impl Default for ProviderConfig {
fn default() -> Self {
Self {
env: Env::default(),
fs: Fs::default(),
connector: default_connector(),
sleep: default_async_sleep(),
region: None,
}
}
}
impl ProviderConfig {
/// Create a default provider config with the region unset.
///
/// Using this option means that you may need to set a region manually.
///
/// This constructor will use a default value for the HTTPS connector and Sleep implementation
/// when they are enabled as crate features which is usually the correct option. To construct
/// a `ProviderConfig` without these fields set, use [`ProviderConfig::empty`].
///
///
/// # Example
/// ```rust
/// use aws_config::provider_config::ProviderConfig;
/// use aws_sdk_sts::Region;
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
/// let conf = ProviderConfig::without_region().with_region(Some(Region::new("us-east-1")));
///
/// # if cfg!(any(feature = "rustls", feature = "native-tls")) {
/// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
/// # }
/// ```
pub fn without_region() -> Self {
Self::default()
}
/// Constructs a ProviderConfig with no fields set
pub fn empty() -> Self {
ProviderConfig {
env: Env::default(),
fs: Fs::default(),
connector: None,
sleep: None,
region: None,
}
}
/// Create a default provider config with the region region automatically loaded from the default chain.
///
/// # Example
/// ```rust
/// # async fn test() {
/// use aws_config::provider_config::ProviderConfig;
/// use aws_sdk_sts::Region;
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
/// let conf = ProviderConfig::with_default_region().await;
/// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
/// }
/// ```
#[cfg(feature = "default-provider")]
pub async fn with_default_region() -> Self {
Self::without_region().load_default_region().await
}
// When all crate features are disabled, these accessors are unused
#[allow(dead_code)]
pub(crate) fn env(&self) -> Env {
self.env.clone()
}
#[allow(dead_code)]
pub(crate) fn fs(&self) -> Fs {
self.fs.clone()
}
#[allow(dead_code)]
pub(crate) fn connector(&self) -> Option<&DynConnector> {
self.connector.as_ref()
}
#[allow(dead_code)]
pub(crate) fn sleep(&self) -> Option<Arc<dyn AsyncSleep>> {
self.sleep.clone()
}
#[allow(dead_code)]
pub(crate) fn region(&self) -> Option<Region> {
self.region.clone()
}
/// Override the region for the configuration
pub fn with_region(mut self, region: Option<Region>) -> Self {
self.region = region;
self
}
#[cfg(feature = "default-provider")]
/// Use the [default region chain](crate::default_provider::region) to set the
/// region for this configuration
///
/// Note: the `env` and `fs` already set on this provider will be used when loading the default region.
pub async fn load_default_region(self) -> Self {
let provider_chain = DefaultRegionChain::builder().configure(&self).build();
self.with_region(provider_chain.region().await)
}
#[doc(hidden)]
pub fn with_fs(self, fs: Fs) -> Self {
ProviderConfig { fs, ..self }
}
#[doc(hidden)]
pub fn with_env(self, env: Env) -> Self {
ProviderConfig { env, ..self }
}
/// Override the HTTPS connector for this configuration
///
/// ## Note: Stability
/// This method is expected to change to support HTTP configuration
pub fn with_connector(self, connector: DynConnector) -> Self {
ProviderConfig {
connector: Some(connector),
..self
}
}
/// Override the sleep implementation for this configuration
pub fn with_sleep(self, sleep: impl AsyncSleep + 'static) -> Self {
ProviderConfig {
sleep: Some(Arc::new(sleep)),
..self
}
}
}

View File

@ -6,13 +6,17 @@
use std::collections::HashMap;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use std::time::{Duration, UNIX_EPOCH};
use crate::provider_config::ProviderConfig;
use aws_types::credentials::{self, ProvideCredentials};
use aws_types::os_shim_internal::{Env, Fs};
use serde::Deserialize;
use smithy_async::rt::sleep::{AsyncSleep, Sleep};
use smithy_client::dvr::{NetworkTraffic, RecordingConnection, ReplayingConnection};
use smithy_client::erase::DynConnector;
use std::future::Future;
/// Test case credentials
///
@ -58,6 +62,19 @@ pub struct TestEnvironment {
base_dir: PathBuf,
}
/// Connector which expects no traffic
pub fn no_traffic_connector() -> DynConnector {
DynConnector::new(ReplayingConnection::new(vec![]))
}
#[derive(Debug)]
struct InstantSleep;
impl AsyncSleep for InstantSleep {
fn sleep(&self, _duration: Duration) -> Sleep {
Sleep::new(std::future::ready(()))
}
}
#[derive(Deserialize)]
enum TestResult {
Ok(Credentials),
@ -97,23 +114,36 @@ impl TestEnvironment {
})
}
pub async fn provider_config(
&self,
) -> (RecordingConnection<ReplayingConnection>, ProviderConfig) {
let connector = RecordingConnection::new(ReplayingConnection::new(
self.network_traffic.events().clone(),
));
(
connector.clone(),
ProviderConfig::empty()
.with_fs(self.fs.clone())
.with_env(self.env.clone())
.with_connector(DynConnector::new(connector.clone()))
.with_sleep(InstantSleep)
.load_default_region()
.await,
)
}
#[allow(dead_code)]
/// Execute the test suite & record a new traffic log
///
/// A connector will be created with the factory, then request traffic will be recorded.
/// Response are generated from the existing http-traffic.json.
pub async fn execute_and_update<P>(&self, make_provider: impl Fn(Fs, Env, DynConnector) -> P)
pub async fn execute_and_update<F, P>(&self, make_provider: impl Fn(ProviderConfig) -> F)
where
F: Future<Output = P>,
P: ProvideCredentials,
{
let connector = RecordingConnection::new(ReplayingConnection::new(
self.network_traffic.events().clone(),
));
let provider = make_provider(
self.fs.clone(),
self.env.clone(),
DynConnector::new(connector.clone()),
);
let (connector, config) = self.provider_config().await;
let provider = make_provider(config).await;
let result = provider.provide_credentials().await;
std::fs::write(
self.base_dir.join("http-traffic-recorded.json"),
@ -128,16 +158,19 @@ impl TestEnvironment {
}
/// Execute a test case. Failures lead to panics.
pub async fn execute<P>(&self, make_provider: impl Fn(Fs, Env, DynConnector) -> P)
pub async fn execute<F, P>(&self, make_provider: impl Fn(ProviderConfig) -> F)
where
F: Future<Output = P>,
P: ProvideCredentials,
{
let connector = ReplayingConnection::new(self.network_traffic.events().clone());
let provider = make_provider(
self.fs.clone(),
self.env.clone(),
DynConnector::new(connector.clone()),
);
let conf = ProviderConfig::empty()
.with_fs(self.fs.clone())
.with_env(self.env.clone())
.with_connector(DynConnector::new(connector.clone()))
.load_default_region()
.await;
let provider = make_provider(conf).await;
let result = provider.provide_credentials().await;
self.log_info();
self.check_results(&result);

View File

@ -33,16 +33,30 @@
//! role_arn = arn:aws:iam::123456789012:role/s3-reader
//! web_identity_token_file = /token.jwt
//! ```
//!
//! # Example
//! Web Identity Token providers are part of the [default chain](crate::default_provider::credentials),
//! however, you can construct one explicitly if you don't want to use the default provider chain:
//!
/// ```rust
/// # async fn test() {
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
/// use aws_config::provider_config::ProviderConfig;
/// let provider = WebIdentityTokenCredentialsProvider::builder()
/// .configure(&ProviderConfig::with_default_region().await)
/// .build();
/// # }
/// ```
use aws_sdk_sts::Region;
use aws_types::os_shim_internal::{Env, Fs};
use smithy_client::erase::DynConnector;
use crate::connector::must_have_connector;
use crate::connector::expect_connector;
use crate::provider_config::ProviderConfig;
use crate::sts;
use aws_types::credentials::{self, future, CredentialsError, ProvideCredentials};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use tracing::Instrument;
const ENV_VAR_TOKEN_FILE: &str = "AWS_WEB_IDENTITY_TOKEN_FILE";
const ENV_VAR_ROLE_ARN: &str = "AWS_ROLE_ARN";
@ -132,6 +146,10 @@ impl WebIdentityTokenCredentialsProvider {
&conf.role_arn,
&conf.session_name,
)
.instrument(tracing::info_span!(
"load_credentials",
provider = "WebIdentityToken"
))
.await
}
}
@ -140,87 +158,52 @@ impl WebIdentityTokenCredentialsProvider {
#[derive(Default)]
pub struct Builder {
source: Option<Source>,
fs: Fs,
connector: Option<DynConnector>,
region: Option<Region>,
config: Option<ProviderConfig>,
}
impl Builder {
#[doc(hidden)]
/// Set the Fs used for this provider
pub fn fs(mut self, fs: Fs) -> Self {
self.fs = fs;
self
}
#[doc(hidden)]
/// Set the Fs used for this provider
pub fn set_fs(&mut self, fs: Fs) -> &mut Self {
self.fs = fs;
self
}
#[doc(hidden)]
/// Set the process environment used for this provider
pub fn env(mut self, env: Env) -> Self {
self.source = Some(Source::Env(env));
self
}
#[doc(hidden)]
/// Set the process environment used for this provider
pub fn set_env(&mut self, env: Env) -> &mut Self {
self.source = Some(Source::Env(env));
/// Configure generic options of the [WebIdentityTokenCredentialsProvider]
///
/// # Example
/// ```rust
/// # async fn test() {
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
/// use aws_config::provider_config::ProviderConfig;
/// let provider = WebIdentityTokenCredentialsProvider::builder()
/// .configure(&ProviderConfig::with_default_region().await)
/// .build();
/// # }
/// ```
pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
self.config = Some(provider_config.clone());
self
}
/// Configure this builder to use [`StaticConfiguration`](StaticConfiguration)
///
/// WebIdentityToken providers load credentials from the file system. They may either determine
/// the path from environment variables (default), or via a statically configured path.
/// WebIdentityToken providers load credentials from the file system. The file system path used
/// may either determine be loaded from environment variables (default), or via a statically
/// configured path.
pub fn static_configuration(mut self, config: StaticConfiguration) -> Self {
self.source = Some(Source::Static(config));
self
}
/// Sets the HTTPS connector used for this provider
pub fn connector(mut self, connector: DynConnector) -> Self {
self.connector = Some(connector);
self
}
/// Sets the HTTPS connector used for this provider
pub fn set_connector(&mut self, connector: Option<DynConnector>) -> &mut Self {
self.connector = connector;
self
}
/// Sets the region used for this provider
pub fn region(mut self, region: Option<Region>) -> Self {
self.region = region;
self
}
/// Sets the region used for this provider
pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
self.region = region;
self
}
/// Build a [`WebIdentityTokenCredentialsProvider`]
///
/// ## Panics
/// If no connector has been enabled via crate features and no connector has been provided via the
/// builder, this function will panic.
pub fn build(self) -> WebIdentityTokenCredentialsProvider {
let connector = self.connector.unwrap_or_else(must_have_connector);
let conf = self.config.unwrap_or_default();
let connector = expect_connector(conf.connector().cloned());
let client = aws_hyper::Client::new(connector);
let source = self.source.unwrap_or_else(|| Source::Env(Env::default()));
let source = self.source.unwrap_or_else(|| Source::Env(conf.env()));
WebIdentityTokenCredentialsProvider {
source,
fs: self.fs,
fs: conf.fs(),
client,
region: self.region,
region: conf.region(),
}
}
}
@ -268,17 +251,20 @@ mod test {
use aws_sdk_sts::Region;
use aws_types::os_shim_internal::{Env, Fs};
use crate::provider_config::ProviderConfig;
use crate::test_case::no_traffic_connector;
use aws_types::credentials::CredentialsError;
use std::collections::HashMap;
#[tokio::test]
async fn unloaded_provider() {
// empty environment
let env = Env::from_slice(&[]);
let provider = Builder::default()
.region(Some(Region::new("us-east-1")))
.env(env)
.build();
let conf = ProviderConfig::empty()
.with_env(Env::from_slice(&[]))
.with_connector(no_traffic_connector())
.with_region(Some(Region::from_static("us-east-1")));
let provider = Builder::default().configure(&conf).build();
let err = provider
.credentials()
.await
@ -292,9 +278,14 @@ mod test {
#[tokio::test]
async fn missing_env_var() {
let env = Env::from_slice(&[(ENV_VAR_TOKEN_FILE, "/token.jwt")]);
let region = Some(Region::new("us-east-1"));
let provider = Builder::default()
.region(Some(Region::new("us-east-1")))
.env(env)
.configure(
&ProviderConfig::empty()
.with_region(region)
.with_env(env)
.with_connector(no_traffic_connector()),
)
.build();
let err = provider
.credentials()
@ -320,9 +311,13 @@ mod test {
]);
let fs = Fs::from_map(HashMap::new());
let provider = Builder::default()
.region(Some(Region::new("us-east-1")))
.fs(fs)
.env(env)
.configure(
&ProviderConfig::empty()
.with_connector(no_traffic_connector())
.with_region(Some(Region::new("us-east-1")))
.with_env(env)
.with_fs(fs),
)
.build();
let err = provider.credentials().await.expect_err("no JWT token");
match err {

View File

@ -2,3 +2,8 @@
region = us-east-1
aws_access_key_id = correct_key
aws_secret_access_key = correct_secret
[profile secondary]
region = us-east-2
aws_access_key_id = correct_key_secondary
aws_secret_access_key = correct_secret_secondary

View File

@ -2,5 +2,5 @@
"AWS_WEB_IDENTITY_TOKEN_FILE": "/token.jwt",
"AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/test-role",
"AWS_ROLE_SESSION_NAME": "test-session",
"AWS_REGION": "us-east-1"
"AWS_REGION": "eu-west-1"
}

View File

@ -7,7 +7,7 @@
"action": {
"Request": {
"request": {
"uri": "https://sts.us-east-1.amazonaws.com/",
"uri": "https://sts.eu-west-1.amazonaws.com/",
"headers": {
"content-type": [
"application/x-www-form-urlencoded"
@ -22,7 +22,7 @@
"aws-sdk-rust/0.1.0 os/macos lang/rust/1.55.0-nightly"
],
"host": [
"sts.us-east-1.amazonaws.com"
"sts.eu-west-1.amazonaws.com"
]
},
"method": "POST"

View File

@ -1,4 +1,4 @@
{
"AWS_REGION": "us-east-1",
"AWS_REGION": "us-east-2",
"HOME": "/home"
}

View File

@ -1,5 +1,6 @@
[default]
region = us-east-2
# this region is overriden by the environment variable
region = us-east-1
role_arn = arn:aws:iam::123456789:role/integration-test
source_profile = base

View File

@ -8,6 +8,7 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
@ -29,7 +30,7 @@ where
/// Returns a default sleep implementation based on the features enabled, or `None` if
/// there isn't one available from this crate.
pub fn default_async_sleep() -> Option<Box<dyn AsyncSleep>> {
pub fn default_async_sleep() -> Option<Arc<dyn AsyncSleep>> {
sleep_tokio()
}
@ -38,7 +39,7 @@ pub fn default_async_sleep() -> Option<Box<dyn AsyncSleep>> {
pub struct Sleep(Pin<Box<dyn Future<Output = ()> + Send + 'static>>);
impl Sleep {
fn new(future: impl Future<Output = ()> + Send + 'static) -> Sleep {
pub fn new(future: impl Future<Output = ()> + Send + 'static) -> Sleep {
Sleep(Box::pin(future))
}
}
@ -72,11 +73,11 @@ impl AsyncSleep for TokioSleep {
}
#[cfg(feature = "rt-tokio")]
fn sleep_tokio() -> Option<Box<dyn AsyncSleep>> {
Some(Box::new(TokioSleep::new()))
fn sleep_tokio() -> Option<Arc<dyn AsyncSleep>> {
Some(Arc::new(TokioSleep::new()))
}
#[cfg(not(feature = "rt-tokio"))]
fn sleep_tokio() -> Option<Box<dyn AsyncSleep>> {
fn sleep_tokio() -> Option<Arc<dyn AsyncSleep>> {
None
}