diff --git a/CHANGELOG.md b/CHANGELOG.md index cb64b8f641..bcfdbd622a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ vNext (Month Day, Year) - Add profile file credential provider implementation. This implementation currently does not support credential sources for assume role providers other than environment variables. (#640) - :bug: Fix name collision that occurred when a model had both a union and a structure named `Result` (#643) +- Add initial implementation of a default provider chain. (#650) +- Update smithy-client to simplify creating HTTP/HTTPS connectors (#650) v0.20 (August 10th, 2021) -------------------------- diff --git a/aws/rust-runtime/aws-auth-providers/Cargo.toml b/aws/rust-runtime/aws-auth-providers/Cargo.toml index a9f5d367bd..9e172dc0cd 100644 --- a/aws/rust-runtime/aws-auth-providers/Cargo.toml +++ b/aws/rust-runtime/aws-auth-providers/Cargo.toml @@ -4,12 +4,20 @@ version = "0.1.0" authors = ["AWS Rust SDK Team ", "Russell Cohen "] edition = "2018" +[features] +rustls = ["smithy-client/rustls"] +native-tls = ["smithy-client/native-tls"] +rt-tokio = ["smithy-async/rt-tokio"] +default = ["rustls", "rt-tokio"] + [dependencies] aws-auth = { path = "../../sdk/build/aws-sdk/aws-auth" } aws-types = { path = "../../sdk/build/aws-sdk/aws-types" } aws-sdk-sts = { path = "../../sdk/build/aws-sdk/sts"} aws-hyper = { path = "../../sdk/build/aws-sdk/aws-hyper"} +smithy-async = { path = "../../sdk/build/aws-sdk/smithy-async" } tracing = "0.1" +smithy-client = { path = "../../sdk/build/aws-sdk/smithy-client" } [dev-dependencies] serde = { version = "1", features = ["derive"] } diff --git a/aws/rust-runtime/aws-auth-providers/src/chain.rs b/aws/rust-runtime/aws-auth-providers/src/chain.rs new file mode 100644 index 0000000000..bfea91337d --- /dev/null +++ b/aws/rust-runtime/aws-auth-providers/src/chain.rs @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +use std::borrow::Cow; + +use aws_auth::provider::{AsyncProvideCredentials, BoxFuture, CredentialsError, CredentialsResult}; +use tracing::Instrument; + +/// Credentials provider that checks a series of inner providers +/// +/// Each provider will be checked in turn. The first provider that returns a successful credential +/// will be used. +/// +/// ## Example +/// ```rust +/// use aws_auth_providers::chain::ChainProvider; +/// use aws_auth::provider::env::EnvironmentVariableCredentialsProvider; +/// use aws_auth::Credentials; +/// let provider = ChainProvider::first_try("Environment", EnvironmentVariableCredentialsProvider::new()) +/// .or_else("Static", Credentials::from_keys("someacceskeyid", "somesecret", None)); +/// ``` +pub struct ChainProvider { + providers: Vec<(Cow<'static, str>, Box)>, +} + +impl ChainProvider { + pub fn first_try( + name: impl Into>, + provider: impl AsyncProvideCredentials + 'static, + ) -> Self { + ChainProvider { + providers: vec![(name.into(), Box::new(provider))], + } + } + + pub fn or_else( + mut self, + name: impl Into>, + provider: impl AsyncProvideCredentials + 'static, + ) -> Self { + self.providers.push((name.into(), Box::new(provider))); + self + } + + async fn credentials(&self) -> CredentialsResult { + let mut last_error = CredentialsError::Unhandled("no providers".into()); + for (name, provider) in &self.providers { + let span = tracing::info_span!("load_credentials", provider = %name); + match provider.provide_credentials().instrument(span).await { + Ok(credentials) => { + tracing::info!(provider = %name, "loaded credentials"); + return Ok(credentials); + } + Err(e) => { + tracing::info!(provider = %name, error = %e, "provider in chain did not provide credentials"); + last_error = e + } + } + } + return Err(last_error); + } +} + +impl AsyncProvideCredentials for ChainProvider { + fn provide_credentials<'a>(&'a self) -> BoxFuture<'a, CredentialsResult> + where + Self: 'a, + { + Box::pin(self.credentials()) + } +} diff --git a/aws/rust-runtime/aws-auth-providers/src/default_provider_chain.rs b/aws/rust-runtime/aws-auth-providers/src/default_provider_chain.rs new file mode 100644 index 0000000000..c91d25851d --- /dev/null +++ b/aws/rust-runtime/aws-auth-providers/src/default_provider_chain.rs @@ -0,0 +1,212 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +use std::borrow::Cow; + +use aws_auth::provider::env::EnvironmentVariableCredentialsProvider; +use aws_auth::provider::lazy_caching::LazyCachingCredentialsProvider; +use aws_auth::provider::BoxFuture; +use aws_auth::provider::{AsyncProvideCredentials, CredentialsResult}; +use aws_hyper::DynConnector; +use aws_types::os_shim_internal::{Env, Fs}; +use aws_types::region::ProvideRegion; +use smithy_async::rt::sleep::AsyncSleep; + +/// Default AWS Credential Provider Chain +/// +/// Resolution order: +/// 1. Environment variables: [`EnvironmentVariableCredentialsProvider`](aws_auth::provider::env::EnvironmentVariableCredentialsProvider) +/// 2. Shared config (`~/.aws/config`, `~/.aws/credentials`): [`SharedConfigCredentialsProvider`](crate::profile::ProfileFileCredentialProvider) +/// +/// The outer provider is wrapped in a refreshing cache. +/// +/// More providers are a work in progress. +/// +/// ## Example: +/// Create a default chain with a custom region: +/// ```rust +/// use aws_types::region::Region; +/// let credentials_provider = aws_auth_providers::DefaultProviderChain::builder() +/// .region(&Region::new("us-west-1")) +/// .build(); +/// ``` +/// +/// Create a default chain with no overrides: +/// ```rust +/// let credentials_provider = aws_auth_providers::default_provider(); +/// ``` +pub struct DefaultProviderChain(LazyCachingCredentialsProvider); + +impl DefaultProviderChain { + pub fn builder() -> Builder { + Builder::default() + } +} + +impl AsyncProvideCredentials for DefaultProviderChain { + fn provide_credentials<'a>(&'a self) -> BoxFuture<'a, CredentialsResult> + where + Self: 'a, + { + self.0.provide_credentials() + } +} + +/// Builder for [`DefaultProviderChain`](DefaultProviderChain) +#[derive(Default)] +pub struct Builder { + profile_file_builder: crate::profile::Builder, + credential_cache: aws_auth::provider::lazy_caching::builder::Builder, + env: Option, +} + +impl Builder { + /// Set the region used when making requests to AWS services (eg. STS) as part of the provider chain + /// + /// When unset, the default region resolver chain will be used. + pub fn region(mut self, region: &dyn ProvideRegion) -> Self { + self.profile_file_builder.set_region(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)); + 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); + self + } + + /// Add an additional credential source for the ProfileProvider + /// + /// Assume role profiles may specify named credential sources: + /// ```ini + /// [default] + /// role_arn = arn:aws:iam::123456789:role/RoleA + /// credential_source = MyCustomProvider + /// ``` + /// + /// Typically, these are built-in providers like `Environment`, however, custom sources may + /// also be used. Using custom sources must be registered: + /// ```rust + /// use aws_auth::provider::{ProvideCredentials, CredentialsError}; + /// use aws_auth::Credentials; + /// use aws_auth_providers::DefaultProviderChain; + /// struct MyCustomProvider; + /// // there is a blanket implementation for `AsyncProvideCredentials` on ProvideCredentials + /// impl ProvideCredentials for MyCustomProvider { + /// fn provide_credentials(&self) -> Result { + /// todo!() + /// } + /// } + /// // assume role can now use `MyCustomProvider` when maed + /// let provider_chain = DefaultProviderChain::builder() + /// .with_custom_credential_source("MyCustomProvider", MyCustomProvider) + /// .build(); + /// ``` + pub fn with_custom_credential_source( + mut self, + name: impl Into>, + provider: impl AsyncProvideCredentials + 'static, + ) -> Self { + self.profile_file_builder = self + .profile_file_builder + .with_custom_provider(name, provider); + self + } + + #[doc(hidden)] + /// Override the filesystem used for this provider + /// + /// This method exists primarily for testing credential providers + pub fn fs(mut self, fs: Fs) -> Self { + self.profile_file_builder.set_fs(Some(fs)); + self + } + + #[doc(hidden)] + /// Override the environment used for this provider + /// + /// This method exists primarily for testing credential providers + pub fn env(mut self, env: Env) -> Self { + self.env = Some(env.clone()); + self.profile_file_builder.set_env(Some(env)); + self + } + + pub fn build(self) -> DefaultProviderChain { + let profile_provider = self.profile_file_builder.build(); + let env_provider = + EnvironmentVariableCredentialsProvider::new_with_env(self.env.unwrap_or_default()); + let provider_chain = crate::chain::ChainProvider::first_try("Environment", env_provider) + .or_else("Profile", profile_provider); + let cached_provider = self.credential_cache.load(provider_chain); + DefaultProviderChain(cached_provider.build()) + } +} + +#[cfg(test)] +mod test { + use crate::DefaultProviderChain; + use aws_auth::provider::AsyncProvideCredentials; + use aws_hyper::DynConnector; + use aws_types::os_shim_internal::{Env, Fs}; + use smithy_client::dvr::ReplayingConnection; + use tracing_test::traced_test; + + #[tokio::test] + async fn prefer_environment() { + let env = Env::from_slice(&[ + ("AWS_ACCESS_KEY_ID", "correct_key"), + ("AWS_SECRET_ACCESS_KEY", "correct_secret"), + ("HOME", "/Users/me"), + ]); + + let fs = Fs::from_test_dir("test-data/aws-config/e2e-assume-role", "/Users/me"); + // empty connection will error if it is used + let connection = ReplayingConnection::new(vec![]); + let provider = DefaultProviderChain::builder() + .fs(fs) + .env(env) + .connector(DynConnector::new(connection)) + .build(); + // empty connection will error if it is used + let creds = provider.provide_credentials().await.expect("valid creds"); + assert_eq!(creds.access_key_id(), "correct_key"); + assert_eq!(creds.secret_access_key(), "correct_secret") + } + + #[traced_test] + #[tokio::test] + async fn fallback_to_profile() { + let env = Env::from_slice(&[ + // access keys not in environment + ("HOME", "/Users/me"), + ]); + + let fs = Fs::from_test_dir("./test-data/static-keys/aws-config", "/Users/me/.aws"); + // empty connection will error if it is used + let connection = ReplayingConnection::new(vec![]); + let provider = DefaultProviderChain::builder() + .fs(fs) + .env(env) + .connector(DynConnector::new(connection)) + .build(); + let creds = provider.provide_credentials().await.expect("valid creds"); + assert_eq!(creds.access_key_id(), "correct_key"); + assert_eq!(creds.secret_access_key(), "correct_secret") + } +} diff --git a/aws/rust-runtime/aws-auth-providers/src/lib.rs b/aws/rust-runtime/aws-auth-providers/src/lib.rs index ed913f8bd8..3e09f6253f 100644 --- a/aws/rust-runtime/aws-auth-providers/src/lib.rs +++ b/aws/rust-runtime/aws-auth-providers/src/lib.rs @@ -2,4 +2,43 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ +use aws_auth::provider::AsyncProvideCredentials; +use aws_hyper::DynConnector; + +pub use default_provider_chain::DefaultProviderChain; + +pub mod default_provider_chain; pub mod profile; + +/// Credentials Provider that evaluates a series of providers +pub mod chain; + +// create a default connector given the currently enabled cargo features. +// rustls | native tls | result +// ----------------------------- +// yes | yes | rustls +// yes | no | rustls +// no | yes | native_tls +// no | no | no default + +#[cfg(feature = "rustls")] +fn default_connector() -> Option { + Some(DynConnector::new(smithy_client::conns::https())) +} + +#[cfg(all(not(feature = "rustls"), feature = "native-tls"))] +fn default_connector() -> Option { + Some(DynConnector::new(smithy_client::conns::native_tls())) +} + +#[cfg(not(any(feature = "rustls", feature = "native-tls")))] +fn default_connector() -> Option { + None +} + +// because this doesn't provide any configuration, a runtime and connector must be provided. +#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "rt-tokio"))] +/// Default AWS provider chain +pub fn default_provider() -> impl AsyncProvideCredentials { + default_provider_chain::Builder::default().build() +} diff --git a/aws/rust-runtime/aws-auth-providers/src/profile.rs b/aws/rust-runtime/aws-auth-providers/src/profile.rs index 2abb00b126..90816484f9 100644 --- a/aws/rust-runtime/aws-auth-providers/src/profile.rs +++ b/aws/rust-runtime/aws-auth-providers/src/profile.rs @@ -33,10 +33,11 @@ use aws_hyper::DynConnector; use aws_sdk_sts::Region; use aws_types::os_shim_internal::{Env, Fs}; use aws_types::profile::ProfileParseError; +use tracing::Instrument; +use crate::default_connector; use crate::profile::exec::named::NamedProviderFactory; use crate::profile::exec::{ClientConfiguration, ProviderChain}; -use tracing::Instrument; mod exec; mod repr; @@ -48,7 +49,7 @@ impl AsyncProvideCredentials for ProfileFileCredentialProvider { { Box::pin(self.load_credentials().instrument(tracing::info_span!( "load_credentials", - provider = "ProfileFile" + provider = "Profile" ))) } } @@ -62,7 +63,7 @@ impl AsyncProvideCredentials for ProfileFileCredentialProvider { /// constructed with the builder: /// ```rust,no_run /// use aws_auth_providers::profile::ProfileFileCredentialProvider; -/// let provider = ProfileFileCredentialProvider::builder().connector(todo!()).build(); +/// let provider = ProfileFileCredentialProvider::builder().build(); /// ``` /// /// This provider supports several different credentials formats: @@ -82,7 +83,7 @@ impl AsyncProvideCredentials for ProfileFileCredentialProvider { /// /// NOTE: Currently only the `Environment` credential source is supported although it is possible to /// provide custom sources: -/// ```rust,no_run +/// ```rust /// use aws_auth_providers::profile::ProfileFileCredentialProvider; /// use aws_auth::provider::{CredentialsResult, AsyncProvideCredentials, BoxFuture}; /// use std::sync::Arc; @@ -99,8 +100,7 @@ impl AsyncProvideCredentials for ProfileFileCredentialProvider { /// } /// } /// let provider = ProfileFileCredentialProvider::builder() -/// .with_custom_provider("Custom", Arc::new(MyCustomProvider) as Arc) -/// .connector(todo!()) +/// .with_custom_provider("Custom", MyCustomProvider) /// .build(); /// ``` /// @@ -151,7 +151,10 @@ impl ProfileFileCredentialProvider { .instrument(tracing::info_span!("load_assume_role", provider = ?provider)) .await; match next_creds { - Ok(next_creds) => creds = next_creds, + Ok(next_creds) => { + tracing::info!(creds = ?next_creds, "loaded assume role credentials"); + creds = next_creds + } Err(e) => { tracing::warn!(provider = ?provider, "failed to load assume role credentials"); return Err(e); @@ -232,7 +235,7 @@ pub struct Builder { env: Option, region: Option, connector: Option, - custom_providers: HashMap>, + custom_providers: HashMap, Arc>, } impl Builder { @@ -241,38 +244,59 @@ impl Builder { self } + pub fn set_fs(&mut self, fs: Option) -> &mut Self { + self.fs = fs; + self + } + pub fn env(mut self, env: Env) -> Self { self.env = Some(env); self } + pub fn set_env(&mut self, env: Option) -> &mut Self { + self.env = env; + self + } + pub fn connector(mut self, connector: DynConnector) -> Self { self.connector = Some(connector); self } + pub fn set_connector(&mut self, connector: Option) -> &mut Self { + self.connector = connector; + self + } + pub fn region(mut self, region: Region) -> Self { self.region = Some(region); self } + pub fn set_region(&mut self, region: Option) -> &mut Self { + self.region = region; + self + } + pub fn with_custom_provider( mut self, - name: impl Into, - provider: impl Into>, + name: impl Into>, + provider: impl AsyncProvideCredentials + 'static, ) -> Self { - self.custom_providers.insert(name.into(), provider.into()); + self.custom_providers + .insert(name.into(), Arc::new(provider)); self } pub fn build(self) -> ProfileFileCredentialProvider { let build_span = tracing::info_span!("build_profile_provider"); let _enter = build_span.enter(); - let fs = self.fs.unwrap_or_else(Fs::real); - let env = self.env.unwrap_or_else(Env::real); + let fs = self.fs.unwrap_or_default(); + let env = self.env.unwrap_or_default(); let mut named_providers = self.custom_providers; named_providers - .entry("Environment".to_string()) + .entry("Environment".into()) .or_insert_with(|| { Arc::new(EnvironmentVariableCredentialsProvider::new_with_env( env.clone(), @@ -281,9 +305,11 @@ impl Builder { // TODO: ECS, IMDS, and other named providers let factory = exec::named::NamedProviderFactory::new(named_providers); let chain = build_provider_chain(&fs, &env, &factory); - let connector = self.connector; + let connector = self.connector.or_else(default_connector).expect( + "a connector must be provided or the `rustls` or `native-tls` features must be enabled", + ); let core_client = aws_hyper::Builder::<()>::new() - .map_connector(|_| connector.expect("a connector must be provided")) + .map_connector(|_| connector) .build(); ProfileFileCredentialProvider { inner: chain, diff --git a/aws/rust-runtime/aws-auth-providers/src/profile/exec.rs b/aws/rust-runtime/aws-auth-providers/src/profile/exec.rs index 6ee879f68f..c58955c4e3 100644 --- a/aws/rust-runtime/aws-auth-providers/src/profile/exec.rs +++ b/aws/rust-runtime/aws-auth-providers/src/profile/exec.rs @@ -144,13 +144,16 @@ pub mod named { use std::sync::Arc; use aws_auth::provider::AsyncProvideCredentials; + use std::borrow::Cow; pub struct NamedProviderFactory { - providers: HashMap>, + providers: HashMap, Arc>, } impl NamedProviderFactory { - pub fn new(providers: HashMap>) -> Self { + pub fn new( + providers: HashMap, Arc>, + ) -> Self { Self { providers } } diff --git a/aws/rust-runtime/aws-auth-providers/test-data/static-keys/aws-config/config b/aws/rust-runtime/aws-auth-providers/test-data/static-keys/aws-config/config new file mode 100644 index 0000000000..911f7ec09d --- /dev/null +++ b/aws/rust-runtime/aws-auth-providers/test-data/static-keys/aws-config/config @@ -0,0 +1,3 @@ +[default] +aws_access_key_id = correct_key +aws_secret_access_key = correct_secret diff --git a/aws/rust-runtime/aws-auth-providers/test-data/static-keys/http-traffic.json b/aws/rust-runtime/aws-auth-providers/test-data/static-keys/http-traffic.json new file mode 100644 index 0000000000..0d325c157c --- /dev/null +++ b/aws/rust-runtime/aws-auth-providers/test-data/static-keys/http-traffic.json @@ -0,0 +1,5 @@ +{ + "events": [], + "docs": "test case with an empty config file, leading to no network requests", + "version": "V0" +} diff --git a/aws/rust-runtime/aws-auth/src/credentials.rs b/aws/rust-runtime/aws-auth/src/credentials.rs index 481970d6b5..f10dfa3000 100644 --- a/aws/rust-runtime/aws-auth/src/credentials.rs +++ b/aws/rust-runtime/aws-auth/src/credentials.rs @@ -40,8 +40,14 @@ struct Inner { impl Debug for Credentials { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut creds = f.debug_struct("Credentials"); - creds.field("provider_name", &self.0.provider_name); - creds.field("access_key_id", &self.0.access_key_id); + creds + .field("provider_name", &self.0.provider_name) + .field("access_key_id", &self.0.access_key_id.as_str()) + .field("secret_access_key", &"** redacted **"); + if let Some(expiry) = self.expiry() { + // TODO: format the expiry nicely + creds.field("expires_after", &expiry); + } creds.finish() } } diff --git a/aws/rust-runtime/aws-types/src/os_shim_internal.rs b/aws/rust-runtime/aws-types/src/os_shim_internal.rs index 0d46d4a00d..6a3cf6da2d 100644 --- a/aws/rust-runtime/aws-types/src/os_shim_internal.rs +++ b/aws/rust-runtime/aws-types/src/os_shim_internal.rs @@ -34,6 +34,12 @@ use std::sync::Arc; /// ``` pub struct Fs(fs::Inner); +impl Default for Fs { + fn default() -> Self { + Fs::real() + } +} + impl Fs { pub fn real() -> Self { Fs(fs::Inner::Real) @@ -106,6 +112,12 @@ mod fs { #[derive(Clone)] pub struct Env(env::Inner); +impl Default for Env { + fn default() -> Self { + Self::real() + } +} + impl Env { pub fn get(&self, k: &str) -> Result { use env::Inner; diff --git a/aws/sdk/examples/s3/Cargo.toml b/aws/sdk/examples/s3/Cargo.toml index 9f581d5ded..95dc11296b 100644 --- a/aws/sdk/examples/s3/Cargo.toml +++ b/aws/sdk/examples/s3/Cargo.toml @@ -9,6 +9,9 @@ edition = "2018" [dependencies] aws-sdk-s3 = { package = "aws-sdk-s3", path = "../../build/aws-sdk/s3" } aws-types = { path = "../../build/aws-sdk/aws-types" } +aws-auth-providers = { path = "../../build/aws-sdk/aws-auth-providers" } + tokio = { version = "1", features = ["full"] } + structopt = { version = "0.3", default-features = false } -tracing-subscriber = "0.2.18" +tracing-subscriber = "0.2.18" \ No newline at end of file diff --git a/aws/sdk/examples/s3/src/bin/list-objects.rs b/aws/sdk/examples/s3/src/bin/list-objects.rs index 65e79483ef..bd25cb97da 100644 --- a/aws/sdk/examples/s3/src/bin/list-objects.rs +++ b/aws/sdk/examples/s3/src/bin/list-objects.rs @@ -5,6 +5,8 @@ use aws_sdk_s3::{Client, Config, Error, Region, PKG_VERSION}; use aws_types::region; + +use aws_auth_providers::DefaultProviderChain; use aws_types::region::ProvideRegion; use structopt::StructOpt; @@ -46,6 +48,7 @@ async fn main() -> Result<(), Error> { .or_else(Region::new("us-west-2")); println!(); + let credential_provider = DefaultProviderChain::builder().region(®ion).build(); if verbose { println!("S3 client version: {}", PKG_VERSION); @@ -54,8 +57,12 @@ async fn main() -> Result<(), Error> { println!(); } - let conf = Config::builder().region(region).build(); - let client = Client::from_conf(conf); + let config = Config::builder() + .region(region) + .credentials_provider(credential_provider) + .build(); + + let client = Client::from_conf(config); let resp = client.list_objects_v2().bucket(&bucket).send().await?; diff --git a/rust-runtime/smithy-async/src/rt/sleep.rs b/rust-runtime/smithy-async/src/rt/sleep.rs index 4ac7dceb82..e376778e91 100644 --- a/rust-runtime/smithy-async/src/rt/sleep.rs +++ b/rust-runtime/smithy-async/src/rt/sleep.rs @@ -17,6 +17,16 @@ pub trait AsyncSleep: std::fmt::Debug + Send + Sync { fn sleep(&self, duration: Duration) -> Sleep; } +impl AsyncSleep for Box +where + T: AsyncSleep, + T: ?Sized, +{ + fn sleep(&self, duration: Duration) -> Sleep { + T::sleep(&self, duration) + } +} + /// 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> { diff --git a/rust-runtime/smithy-client/src/dvr/record.rs b/rust-runtime/smithy-client/src/dvr/record.rs index 074feefa6a..416cc038a9 100644 --- a/rust-runtime/smithy-client/src/dvr/record.rs +++ b/rust-runtime/smithy-client/src/dvr/record.rs @@ -10,8 +10,6 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::task::{Context, Poll}; use http_body::Body; -use hyper::client::HttpConnector; -use hyper_rustls::HttpsConnector; use tokio::task::JoinHandle; use tower::{BoxError, Service}; @@ -31,15 +29,13 @@ pub struct RecordingConnection { pub(crate) inner: S, } -impl RecordingConnection, SdkBody>> { +impl RecordingConnection { /// Construct a recording connection wrapping a default HTTPS implementation #[cfg(feature = "hyper-rustls")] pub fn https() -> Self { - let https = hyper_rustls::HttpsConnector::with_native_roots(); - let client = hyper::Client::builder().build::<_, SdkBody>(https); Self { data: Default::default(), - inner: client, + inner: crate::conns::https(), num_events: Arc::new(AtomicUsize::new(0)), } } diff --git a/rust-runtime/smithy-client/src/hyper_impls.rs b/rust-runtime/smithy-client/src/hyper_impls.rs index 041a59bf21..e3b239da21 100644 --- a/rust-runtime/smithy-client/src/hyper_impls.rs +++ b/rust-runtime/smithy-client/src/hyper_impls.rs @@ -83,9 +83,7 @@ impl Builder<(), M, R> { self, ) -> Builder>, M, R> { - let https = hyper_rustls::HttpsConnector::with_native_roots(); - let client = hyper::Client::builder().build::<_, SdkBody>(https); - self.connector(HyperAdapter::from(client)) + self.connector(crate::conns::https()) } /// Connect to the service over HTTPS using Rustls. @@ -105,8 +103,6 @@ impl Builder<(), M, R> { pub fn native_tls( self, ) -> Builder>, M, R> { - let https = hyper_tls::HttpsConnector::new(); - let client = hyper::Client::builder().build::<_, SdkBody>(https); - self.connector(HyperAdapter::from(client)) + self.connector(crate::conns::native_tls()) } } diff --git a/rust-runtime/smithy-client/src/lib.rs b/rust-runtime/smithy-client/src/lib.rs index 223ad95a08..6454d68305 100644 --- a/rust-runtime/smithy-client/src/lib.rs +++ b/rust-runtime/smithy-client/src/lib.rs @@ -43,11 +43,27 @@ pub mod static_tests; #[cfg(feature = "hyper")] #[allow(missing_docs)] pub mod conns { + use smithy_http::body::SdkBody; + #[cfg(feature = "rustls")] pub type Https = crate::hyper_impls::HyperAdapter< hyper_rustls::HttpsConnector, >; + #[cfg(feature = "rustls")] + pub fn https() -> Https { + let https = hyper_rustls::HttpsConnector::with_native_roots(); + let client = hyper::Client::builder().build::<_, SdkBody>(https); + crate::hyper_impls::HyperAdapter::from(client) + } + + #[cfg(feature = "native-tls")] + pub fn native_tls() -> NativeTls { + let https = hyper_tls::HttpsConnector::new(); + let client = hyper::Client::builder().build::<_, SdkBody>(https); + crate::hyper_impls::HyperAdapter::from(client) + } + #[cfg(feature = "native-tls")] pub type NativeTls = crate::hyper_impls::HyperAdapter>;