diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 17aede3d2e251e5289aa8f1a099760903058c2df..7a81809aec67d5aa72beb928cc378ca3e07840c9 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -16,3 +16,357 @@ message = "`@sparse` list shapes and map shapes with constraint traits and with references = ["smithy-rs#2213"] meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "server"} author = "david-perez" + +[[aws-sdk-rust]] +message = """Integrate Endpoints 2.0 into the Rust SDK. Endpoints 2.0 enables features like S3 virtual addressing & S3 +object lambda. As part of this change, there are several breaking changes although efforts have been made to deprecate +where possible to smooth the upgrade path. +1. `aws_smithy_http::endpoint::Endpoint` and the `endpoint_resolver` methods have been deprecated. In general, these usages + should be replaced with usages of `endpoint_url` instead. `endpoint_url` accepts a string so an `aws_smithy_http::Endpoint` + does not need to be constructed. This structure and methods will be removed in a future release. +2. The `endpoint_resolver` method on `::config::Builder` now accepts a service specific endpoint resolver instead + of an implementation of `ResolveAwsEndpoint`. Most users will be able to replace these usages with a usage of `endpoint_url`. +3. `ResolveAwsEndpoint` has been deprecated and will be removed in a future version of the SDK. +4. The SDK does not support "pseudo regions" anymore. Specifically, regions like `iam-fips` will no longer resolve to a FIPS endpoint. +""" +references = ["smithy-rs#1784", "smithy-rs#2074"] +meta = { "breaking" = true, "tada" = true, "bug" = false } +author = "rcoh" + +[[aws-sdk-rust]] +message = """Add additional configuration parameters to `aws_sdk_s3::Config`. + +The launch of endpoints 2.0 includes more configuration options for S3. The default behavior for endpoint resolution has +been changed. Before, all requests hit the path-style endpoint. Going forward, all requests that can be routed to the +virtually hosted bucket will be routed there automatically. +- `force_path_style`: Requests will now default to the virtually-hosted endpoint `.s3..amazonaws.com` +- `use_arn_region`: Enables this client to use an ARN’s region when constructing an endpoint instead of the client’s configured region. +- `accelerate`: Enables this client to use S3 Transfer Acceleration endpoints. + +Note: the AWS SDK for Rust does not currently support Multi Region Access Points (MRAP). +""" +references = ["smithy-rs#1784", "smithy-rs#2074"] +meta = { "breaking" = true, "tada" = true, "bug" = false } +author = "rcoh" + +[[smithy-rs]] +message = "In 0.52, `@length`-constrained collection shapes whose members are not constrained made the server code generator crash. This has been fixed." +references = ["smithy-rs#2103"] +meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "server" } +author = "david-perez" + +[[smithy-rs]] +message = "The Rust client codegen plugin is now called `rust-client-codegen` instead of `rust-codegen`. Be sure to update your `smithy-build.json` files to refer to the correct plugin name." +references = ["smithy-rs#2099"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" + +[[smithy-rs]] +message = "Client codegen plugins need to define a service named `software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator` (this is the new file name for the plugin definition in `resources/META-INF/services`)." +references = ["smithy-rs#2099"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" + +[[smithy-rs]] +message = "Server codegen plugins need to define a service named `software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator` (this is the new file name for the plugin definition in `resources/META-INF/services`)." +references = ["smithy-rs#2099"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" } +author = "jdisanti" + +[[aws-sdk-rust]] +message = """ +Move types for AWS SDK credentials to a separate crate. +A new AWS runtime crate called `aws-credential-types` has been introduced. Types for AWS SDK credentials have been moved to that crate from `aws-config` and `aws-types`. The new crate is placed at the top of the dependency graph among AWS runtime crates with the aim of the downstream crates having access to the types defined in it. +""" +references = ["smithy-rs#2108"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "ysaito1001" + +[[smithy-rs]] +message = "Servers support the `@default` trait: models can specify default values. Default values will be automatically supplied when not manually set." +references = ["smithy-rs#1879"] +meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "server" } +author = "82marbag" + +[[smithy-rs]] +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; +/// ``` +""" + +[[smithy-rs]] +message = "Fix bug where string default values were not supported for endpoint parameters" +references = ["smithy-rs#2150"] +meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client" } +author = "rcoh" + +[[aws-sdk-rust]] +references = ["smithy-rs#2162"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +message = "`aws_config::profile::retry_config` && `aws_config::environment::retry_config` have been removed. Use `aws_config::default_provider::retry_config` instead." +author = "rcoh" + +[[smithy-rs]] +references = ["smithy-rs#2170", "aws-sdk-rust#706"] +meta = { "breaking" = false, "tada" = false, "bug" = true } +message = "Remove the webpki-roots feature from `hyper-rustls`" +author = "rcoh" + +[[aws-sdk-rust]] +references = ["smithy-rs#2168"] +meta = { "breaking" = false, "tada" = true, "bug" = false } +message = """Add support for resolving FIPS and dual-stack endpoints. + +FIPS and dual-stack endpoints can each be configured in multiple ways: +1. Automatically from the environment and AWS profile +2. Across all clients loaded from the same `SdkConfig` via `from_env().use_dual_stack(true).load().await` +3. At a client level when constructing the configuration for an individual client. + +Note: Not all services support FIPS and dual-stack. +""" +author = "rcoh" + +[[aws-sdk-rust]] +message = """ +Improve SDK credentials caching through type safety. `LazyCachingCredentialsProvider` has been renamed to `LazyCredentialsCache` and is no longer treated as a credentials provider. Furthermore, you do not create a `LazyCredentialsCache` directly, and instead you interact with `CredentialsCache`. This introduces the following breaking changes. + +If you previously used `LazyCachingCredentialsProvider`, you can replace it with `CredentialsCache`. +
+Example + +Before: +```rust +use aws_config::meta::credentials::lazy_caching::LazyCachingCredentialsProvider; +use aws_types::provider::ProvideCredentials; + +fn make_provider() -> impl ProvideCredentials { + // --snip-- +} + +let credentials_provider = + LazyCachingCredentialsProvider::builder() + .load(make_provider()) + .build(); + +let sdk_config = aws_config::from_env() + .credentials_provider(credentials_provider) + .load() + .await; + +let client = aws_sdk_s3::Client::new(&sdk_config); +``` + +After: +```rust +use aws_credential_types::cache::CredentialsCache; +use aws_types::provider::ProvideCredentials; +use std::sync::Arc; + +fn make_provider() -> impl ProvideCredentials { + // --snip-- +} + +// Wrapping a result of `make_provider` in `LazyCredentialsCache` is done automatically. +let sdk_config = aws_config::from_env() + .credentials_cache(CredentialsCache::lazy()) // This line can be omitted because it is on by default. + .credentials_provider(Arc::new(make_provider())) + .load() + .await; + +let client = aws_sdk_s3::Client::new(&sdk_config); +``` + +If you previously configured a `LazyCachingCredentialsProvider`, you can use the builder for `LazyCredentialsCache` instead. + +Before: +```rust +use aws_config::meta::credentials::lazy_caching::LazyCachingCredentialsProvider; +use aws_types::provider::ProvideCredentials; +use std::time::Duration; + +fn make_provider() -> impl ProvideCredentials { + // --snip-- +} + +let credentials_provider = + LazyCachingCredentialsProvider::builder() + .load(make_provider()) + .load_timeout(Duration::from_secs(60)) // Configures timeout. + .build(); + +let sdk_config = aws_config::from_env() + .credentials_provider(credentials_provider) + .load() + .await; + +let client = aws_sdk_s3::Client::new(&sdk_config); +``` + +After: +```rust +use aws_credential_types::cache::CredentialsCache; +use aws_types::provider::ProvideCredentials; +use std::sync::Arc; +use std::time::Duration; + +fn make_provider() -> impl ProvideCredentials { + // --snip-- +} + +let sdk_config = aws_config::from_env() + .credentials_cache( + CredentialsCache::lazy_builder() + .load_timeout(Duration::from_secs(60)) // Configures timeout. + .into_credentials_cache(), + ) + .credentials_provider(Arc::new(make_provider())) + .load() + .await; + +let client = aws_sdk_s3::Client::new(&sdk_config); +``` + +The examples above only demonstrate how to use `credentials_cache` and `credentials_provider` methods on `aws_config::ConfigLoader` but the same code update can be applied when you interact with `aws_types::sdk_config::Builder` or the builder for a service-specific config, e.g. `aws_sdk_s3::config::Builder`. + +
+ + +If you previously configured a `DefaultCredentialsChain` by calling `load_timeout`, `buffer_time`, or `default_credential_expiration` on its builder, you need to call the same set of methods on the builder for `LazyCredentialsCache` instead. +
+Example + +Before: +```rust +use aws_config::default_provider::credentials::DefaultCredentialsChain; +use std::time::Duration; + +let credentials_provider = DefaultCredentialsChain::builder() + .buffer_time(Duration::from_secs(30)) + .default_credential_expiration(Duration::from_secs(20 * 60)) + .build() + .await; + +let sdk_config = aws_config::from_env() + .credentials_provider(credentials_provider) + .load() + .await; + +let client = aws_sdk_s3::Client::new(&sdk_config); +``` + +After: +```rust +use aws_config::default_provider::credentials::default_provider; +use aws_credential_types::cache::CredentialsCache; +use std::sync::Arc; +use std::time::Duration; + +// Previously used methods no longer exist on the builder for `DefaultCredentialsChain`. +let credentials_provider = default_provider().await; + +let sdk_config = aws_config::from_env() + .credentials_cache( + CredentialsCache::lazy_builder() + .buffer_time(Duration::from_secs(30)) + .default_credential_expiration(Duration::from_secs(20 * 60)) + .into_credentials_cache(), + ) + .credentials_provider(Arc::new(credentials_provider)) + .load() + .await; + +let client = aws_sdk_s3::Client::new(&sdk_config); +``` + +
+""" +references = ["smithy-rs#2122"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "ysaito1001" + +[[aws-sdk-rust]] +message = """ +The introduction of `CredentialsCache` comes with an accompanying type `SharedCredentialsCache`. This replaces `SharedCredentialsProvider` and as a result, `aws_http::auth:set_provider` has been updated to `aws_http::auth::set_credentials_cache`. + +Before: +```rust +use aws_credential_types::Credentials; +use aws_credential_types::provider::SharedCredentialsProvider; +use aws_http::auth::set_provider; +use aws_smithy_http::body::SdkBody; +use aws_smithy_http::operation; + +let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body"))); +let credentials = Credentials::new("example", "example", None, None, "my_provider_name"); +set_provider( + &mut req.properties_mut(), + SharedCredentialsProvider::new(credentials), +); +``` + +After: +```rust +use aws_credential_types::Credentials; +use aws_credential_types::cache::{CredentialsCache, SharedCredentialsCache}; +use aws_http::auth::set_credentials_cache; +use aws_smithy_http::body::SdkBody; +use aws_smithy_http::operation; +use std::sync::Arc; + +let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body"))); +let credentials = Credentials::new("example", "example", None, None, "my_provider_name"); +let credentials_cache = CredentialsCache::lazy_builder() + .into_credentials_cache() + .create_cache(Arc::new(credentials)); +set_credentials_cache( + &mut req.properties_mut(), + SharedCredentialsCache::new(credentials_cache), +); +``` +""" +references = ["smithy-rs#2122"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "ysaito1001" diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index e464c19c58b3f20149a6b8b406077d6f34c146d4..e47352e1519c30336f0b0781ff46a554347a6b54 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -3,9 +3,9 @@ # require manual version bumping every time an automated version bump # to the exposed SDK crates happens. allowed_external_types = [ + "aws_credential_types::cache::CredentialsCache", "aws_credential_types::provider::ProvideCredentials", "aws_credential_types::provider::Result", - "aws_credential_types::provider::SharedCredentialsProvider", "aws_sdk_sts::model::PolicyDescriptorType", "aws_smithy_async::rt::sleep::AsyncSleep", "aws_smithy_client::bounds::SmithyConnector", diff --git a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs index 07585847356dd76c9f425237c0b00f807f323d53..c2a31b5e7940943ee88e0ef8e2c55802980420c4 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs @@ -4,9 +4,7 @@ */ use std::borrow::Cow; -use std::time::Duration; -use aws_credential_types::lazy_caching::{self, LazyCachingCredentialsProvider}; use aws_credential_types::provider::{self, future, ProvideCredentials}; use tracing::Instrument; @@ -60,7 +58,9 @@ pub async fn default_provider() -> impl ProvideCredentials { /// .build(); /// ``` #[derive(Debug)] -pub struct DefaultCredentialsChain(LazyCachingCredentialsProvider); +pub struct DefaultCredentialsChain { + provider_chain: CredentialsProviderChain, +} impl DefaultCredentialsChain { /// Builder for `DefaultCredentialsChain` @@ -69,7 +69,7 @@ impl DefaultCredentialsChain { } async fn credentials(&self) -> provider::Result { - self.0 + self.provider_chain .provide_credentials() .instrument(tracing::debug_span!("provide_credentials", provider = %"default_chain")) .await @@ -92,7 +92,6 @@ pub struct Builder { web_identity_builder: crate::web_identity_token::Builder, imds_builder: crate::imds::credentials::Builder, ecs_builder: crate::ecs::Builder, - credential_cache: lazy_caching::Builder, region_override: Option>, region_chain: crate::default_provider::region::Builder, conf: Option, @@ -115,71 +114,6 @@ impl Builder { self } - /// Timeout for the entire credential loading chain. - /// - /// Defaults to 5 seconds. - pub fn load_timeout(mut self, timeout: Duration) -> Self { - self.set_load_timeout(Some(timeout)); - self - } - - /// Timeout for the entire credential loading chain. - /// - /// Defaults to 5 seconds. - pub fn set_load_timeout(&mut self, timeout: Option) -> &mut Self { - self.credential_cache.set_load_timeout(timeout); - self - } - - /// Amount of time before the actual credential expiration time - /// where credentials are considered expired. - /// - /// For example, if credentials are expiring in 15 minutes, and the buffer time is 10 seconds, - /// then any requests made after 14 minutes and 50 seconds will load new credentials. - /// - /// Defaults to 10 seconds. - pub fn buffer_time(mut self, buffer_time: Duration) -> Self { - self.set_buffer_time(Some(buffer_time)); - self - } - - /// Amount of time before the actual credential expiration time - /// where credentials are considered expired. - /// - /// For example, if credentials are expiring in 15 minutes, and the buffer time is 10 seconds, - /// then any requests made after 14 minutes and 50 seconds will load new credentials. - /// - /// Defaults to 10 seconds. - pub fn set_buffer_time(&mut self, buffer_time: Option) -> &mut Self { - self.credential_cache.set_buffer_time(buffer_time); - self - } - - /// Default expiration time to set on credentials if they don't have an expiration time. - /// - /// This is only used if the given [`ProvideCredentials`] returns - /// [`Credentials`](aws_credential_types::Credentials) that don't have their `expiry` set. - /// This must be at least 15 minutes. - /// - /// Defaults to 15 minutes. - pub fn default_credential_expiration(mut self, duration: Duration) -> Self { - self.set_default_credential_expiration(Some(duration)); - self - } - - /// Default expiration time to set on credentials if they don't have an expiration time. - /// - /// This is only used if the given [`ProvideCredentials`] returns - /// [`Credentials`](aws_credential_types::Credentials) that don't have their `expiry` set. - /// This must be at least 15 minutes. - /// - /// Defaults to 15 minutes. - pub fn set_default_credential_expiration(&mut self, duration: Option) -> &mut Self { - self.credential_cache - .set_default_credential_expiration(duration); - self - } - /// Add an additional credential source for the ProfileProvider /// /// Assume role profiles may specify named credential sources: @@ -252,12 +186,8 @@ impl Builder { .or_else("WebIdentityToken", web_identity_token_provider) .or_else("EcsContainer", ecs_provider) .or_else("Ec2InstanceMetadata", imds_provider); - let cached_provider = self - .credential_cache - .configure(conf.sleep(), conf.time_source()) - .load(provider_chain); - DefaultCredentialsChain(cached_provider.build()) + DefaultCredentialsChain { provider_chain } } } diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index cc91fda51258db999b6bb30fe9d576ae8c7c64b3..d1b93f8993c0e8378b5a13dc6a6feb46072e8a1b 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -150,7 +150,8 @@ pub async fn load_from_env() -> aws_types::SdkConfig { mod loader { use std::sync::Arc; - use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; + use aws_credential_types::cache::CredentialsCache; + use aws_credential_types::provider::ProvideCredentials; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep}; use aws_smithy_client::http_connector::{ConnectorSettings, HttpConnector}; use aws_smithy_types::retry::RetryConfig; @@ -177,7 +178,8 @@ mod loader { #[derive(Default, Debug)] pub struct ConfigLoader { app_name: Option, - credentials_provider: Option, + credentials_cache: Option, + credentials_provider: Option>, endpoint_resolver: Option>, endpoint_url: Option, region: Option>, @@ -298,6 +300,25 @@ mod loader { self } + /// Override the credentials cache used to build [`SdkConfig`](aws_types::SdkConfig). + /// + /// # Examples + /// + /// Override the credentials cache but load the default value for region: + /// ```no_run + /// # use aws_credential_types::cache::CredentialsCache; + /// # async fn create_config() { + /// let config = aws_config::from_env() + /// .credentials_cache(CredentialsCache::lazy()) + /// .load() + /// .await; + /// # } + /// ``` + pub fn credentials_cache(mut self, credentials_cache: CredentialsCache) -> Self { + self.credentials_cache = Some(credentials_cache); + self + } + /// Override the credentials provider used to build [`SdkConfig`](aws_types::SdkConfig). /// /// # Examples @@ -305,21 +326,22 @@ mod loader { /// Override the credentials provider but load the default value for region: /// ```no_run /// # use aws_credential_types::Credentials; + /// # use std::sync::Arc; /// # fn create_my_credential_provider() -> Credentials { /// # Credentials::new("example", "example", None, None, "example") /// # } /// # async fn create_config() { /// let config = aws_config::from_env() - /// .credentials_provider(create_my_credential_provider()) + /// .credentials_provider(Arc::new(create_my_credential_provider())) /// .load() /// .await; /// # } /// ``` pub fn credentials_provider( mut self, - credentials_provider: impl ProvideCredentials + 'static, + credentials_provider: Arc, ) -> Self { - self.credentials_provider = Some(SharedCredentialsProvider::new(credentials_provider)); + self.credentials_provider = Some(credentials_provider); self } @@ -524,7 +546,9 @@ mod loader { .await }; - let sleep_impl = if self.sleep.is_none() { + let sleep_impl = if self.sleep.is_some() { + self.sleep + } else { if default_async_sleep().is_none() { tracing::warn!( "An implementation of AsyncSleep was requested by calling default_async_sleep \ @@ -535,8 +559,6 @@ mod loader { ); } default_async_sleep() - } else { - self.sleep }; let timeout_config = if let Some(timeout_config) = self.timeout_config { @@ -548,14 +570,18 @@ mod loader { .await }; - let http_connector = if let Some(http_connector) = self.http_connector { - http_connector - } else { + let http_connector = self.http_connector.unwrap_or_else(|| { HttpConnector::Prebuilt(default_connector( &ConnectorSettings::from_timeout_config(&timeout_config), sleep_impl.clone(), )) - }; + }); + + let credentials_cache = self.credentials_cache.unwrap_or_else(|| { + let mut builder = CredentialsCache::lazy_builder().time_source(conf.time_source()); + builder.set_sleep(conf.sleep()); + builder.into_credentials_cache() + }); let use_fips = if let Some(use_fips) = self.use_fips { Some(use_fips) @@ -574,7 +600,7 @@ mod loader { } else { let mut builder = credentials::DefaultCredentialsChain::builder().configure(conf); builder.set_region(region.clone()); - SharedCredentialsProvider::new(builder.build().await) + Arc::new(builder.build().await) }; let endpoint_resolver = self.endpoint_resolver; @@ -583,6 +609,7 @@ mod loader { .region(region) .retry_config(retry_config) .timeout_config(timeout_config) + .credentials_cache(credentials_cache) .credentials_provider(credentials_provider) .http_connector(http_connector); diff --git a/aws/rust-runtime/aws-config/src/profile/credentials.rs b/aws/rust-runtime/aws-config/src/profile/credentials.rs index c429c85636a604ed1c8d0752261da006efff5e2e..845197f95ecb42c6b8ea0967adc03f5a4e034944 100644 --- a/aws/rust-runtime/aws-config/src/profile/credentials.rs +++ b/aws/rust-runtime/aws-config/src/profile/credentials.rs @@ -62,7 +62,7 @@ impl ProvideCredentials for ProfileFileCredentialsProvider { /// ``` /// /// _Note: Profile providers to not implement any caching. They will reload and reparse the profile -/// from the file system when called. See [lazy_caching](aws_credential_types::lazy_caching::LazyCachingCredentialsProvider) for +/// from the file system when called. See [CredentialsCache](aws_credential_types::cache::CredentialsCache) for /// more information about caching._ /// /// This provider supports several different credentials formats: 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 ac7dee3764b658738c5716fee1cc78fc08b0d357..a90472c8e6ce15ee02c7f4e51d2e727b23fa112f 100644 --- a/aws/rust-runtime/aws-config/src/profile/profile_file.rs +++ b/aws/rust-runtime/aws-config/src/profile/profile_file.rs @@ -23,6 +23,7 @@ use std::path::PathBuf; /// ``` /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider}; /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind}; +/// use std::sync::Arc; /// /// # async fn example() { /// let profile_files = ProfileFiles::builder() diff --git a/aws/rust-runtime/aws-config/src/sts/assume_role.rs b/aws/rust-runtime/aws-config/src/sts/assume_role.rs index 8b4da9466c26db1277464e416ff248445d7f05e1..422b6441515e124faa2fd79416534fb3ed4c22e0 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -5,10 +5,8 @@ //! Assume credentials for a role through the AWS Security Token Service (STS). -use aws_credential_types::lazy_caching::LazyCachingCredentialsProvider; -use aws_credential_types::provider::{ - self, error::CredentialsError, future, ProvideCredentials, SharedCredentialsProvider, -}; +use aws_credential_types::cache::CredentialsCache; +use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; use aws_sdk_sts::error::AssumeRoleErrorKind; use aws_sdk_sts::middleware::DefaultMiddleware; use aws_sdk_sts::model::PolicyDescriptorType; @@ -45,7 +43,7 @@ use tracing::Instrument; /// ``` #[derive(Debug)] pub struct AssumeRoleProvider { - cache: LazyCachingCredentialsProvider, + inner: Inner, } #[derive(Debug)] @@ -81,6 +79,7 @@ pub struct AssumeRoleProviderBuilder { session_length: Option, policy: Option, policy_arns: Option>, + credentials_cache: Option, } impl AssumeRoleProviderBuilder { @@ -101,6 +100,7 @@ impl AssumeRoleProviderBuilder { conf: None, policy: None, policy_arns: None, + credentials_cache: None, } } @@ -181,6 +181,12 @@ impl AssumeRoleProviderBuilder { self } + /// Set the [`CredentialsCache`] for credentials retrieved from STS. + pub fn credentials_cache(mut self, cache: CredentialsCache) -> Self { + self.credentials_cache = Some(cache); + self + } + /// Override the configuration used for this provider /// /// This enables overriding the connection used to communicate with STS in addition to other internal @@ -191,10 +197,18 @@ impl AssumeRoleProviderBuilder { } /// Build a credentials provider for this role authorized by the given `provider`. - pub fn build(self, provider: impl Into) -> AssumeRoleProvider { + pub fn build(self, provider: impl ProvideCredentials + 'static) -> AssumeRoleProvider { let conf = self.conf.unwrap_or_default(); + + let credentials_cache = self.credentials_cache.unwrap_or_else(|| { + let mut builder = CredentialsCache::lazy_builder().time_source(conf.time_source()); + builder.set_sleep(conf.sleep()); + builder.into_credentials_cache() + }); + let config = aws_sdk_sts::Config::builder() - .credentials_provider(provider.into()) + .credentials_cache(credentials_cache) + .credentials_provider(provider) .region(self.region.clone()) .build(); @@ -221,16 +235,13 @@ impl AssumeRoleProviderBuilder { .build() .expect("operation is valid"); - let inner = Inner { - sts: client, - conf: config, - op: operation, - }; - let cache = LazyCachingCredentialsProvider::builder() - .configure(conf.sleep(), conf.time_source()) - .load(inner) - .build(); - AssumeRoleProvider { cache } + AssumeRoleProvider { + inner: Inner { + sts: client, + conf: config, + op: operation, + }, + } } } @@ -273,37 +284,30 @@ impl Inner { } } -impl ProvideCredentials for Inner { +impl ProvideCredentials for AssumeRoleProvider { fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'_> where Self: 'a, { future::ProvideCredentials::new( - self.credentials() + self.inner + .credentials() .instrument(tracing::debug_span!("assume_role")), ) } } -impl ProvideCredentials for AssumeRoleProvider { - fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> - where - Self: 'a, - { - self.cache.provide_credentials() - } -} - #[cfg(test)] mod test { use crate::provider_config::ProviderConfig; use crate::sts::AssumeRoleProvider; - use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; + use aws_credential_types::credential_fn::provide_credentials_fn; + use aws_credential_types::provider::ProvideCredentials; use aws_credential_types::time_source::{TestingTimeSource, TimeSource}; use aws_credential_types::Credentials; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::capture_request; + use aws_smithy_client::test_connection::{capture_request, TestConnection}; use aws_smithy_http::body::SdkBody; use aws_types::region::Region; use std::time::{Duration, UNIX_EPOCH}; @@ -321,7 +325,9 @@ mod test { .configure(&provider_conf) .region(Region::new("us-east-1")) .session_length(Duration::from_secs(1234567)) - .build(SharedCredentialsProvider::new(Credentials::for_tests())); + .build(provide_credentials_fn(|| async { + Ok(Credentials::for_tests()) + })); let _ = provider.provide_credentials().await; let req = request.expect_request(); let str_body = std::str::from_utf8(req.body().bytes().unwrap()).unwrap(); @@ -330,25 +336,34 @@ mod test { #[tokio::test] async fn provider_caches_credentials() { - let resp = http::Response::new(SdkBody::from( - "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/imds-chained-role-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n secretkeycorrect\n tokencorrect\n 2009-02-13T23:31:30Z\n \n \n \n d9d47248-fd55-4686-ad7c-0fb7cd1cddd7\n \n\n", - )); - let (server, _request) = capture_request(Some(resp)); + let conn = TestConnection::new(vec![ + (http::Request::new(SdkBody::from("request body")), + http::Response::builder().status(200).body(SdkBody::from( + "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/imds-chained-role-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n secretkeycorrect\n tokencorrect\n 2009-02-13T23:31:30Z\n \n \n \n d9d47248-fd55-4686-ad7c-0fb7cd1cddd7\n \n\n" + )).unwrap()), + (http::Request::new(SdkBody::from("request body")), + http::Response::builder().status(200).body(SdkBody::from( + "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/imds-chained-role-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n secretkeycorrect\n tokencorrect\n 2009-02-13T23:31:30Z\n \n \n \n d9d47248-fd55-4686-ad7c-0fb7cd1cddd7\n \n\n" + )).unwrap()), + ]); let provider_conf = ProviderConfig::empty() .with_sleep(TokioSleep::new()) .with_time_source(TimeSource::testing(&TestingTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), ))) - .with_http_connector(DynConnector::new(server)); + .with_http_connector(DynConnector::new(conn)); let provider = AssumeRoleProvider::builder("myrole") .configure(&provider_conf) .region(Region::new("us-east-1")) - .build(SharedCredentialsProvider::new(Credentials::for_tests())); + .build(provide_credentials_fn(|| async { + Ok(Credentials::for_tests()) + })); let creds_first = provider .provide_credentials() .await .expect("should return valid credentials"); - + // The effect of caching is implicitly enabled by a `LazyCredentialsCache` + // baked in the configuration for STS stored in `provider.inner.conf`. let creds_second = provider .provide_credentials() .await diff --git a/aws/rust-runtime/aws-credential-types/README.md b/aws/rust-runtime/aws-credential-types/README.md index fa40e5695264275ef39ed08467a719c5eca0636c..e77385e1a01e0f09c12a487e3f764c0f58daeb61 100644 --- a/aws/rust-runtime/aws-credential-types/README.md +++ b/aws/rust-runtime/aws-credential-types/README.md @@ -1,7 +1,7 @@ # aws-credential-types This crate provides types concerned with AWS SDK credentials including: -* A trait for credentials providers +* Traits for credentials providers and for credentials caching * An opaque struct representing credentials * Concrete implementations of credentials caching diff --git a/aws/rust-runtime/aws-credential-types/src/cache.rs b/aws/rust-runtime/aws-credential-types/src/cache.rs index 5387dd402cd2d60af7a7c58a04984f56e4644bae..6a6c740d06c2e979e9c161d2f4b472c21e4e3382 100644 --- a/aws/rust-runtime/aws-credential-types/src/cache.rs +++ b/aws/rust-runtime/aws-credential-types/src/cache.rs @@ -3,160 +3,111 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Expiry-aware cache +//! Types and traits for enabling caching -use std::future::Future; -use std::marker::PhantomData; +mod expiring_cache; +mod lazy_caching; + +pub use expiring_cache::ExpiringCache; +pub use lazy_caching::Builder as LazyBuilder; + +use crate::provider::{future, ProvideCredentials}; use std::sync::Arc; -use std::time::{Duration, SystemTime}; -use tokio::sync::{OnceCell, RwLock}; -/// [`ExpiringCache`] implements two important features: -/// 1. Respect expiry of contents -/// 2. Deduplicate load requests to prevent thundering herds when no value is present. -#[derive(Debug)] -pub struct ExpiringCache { - /// Amount of time before the actual expiration time - /// when the value is considered expired. - buffer_time: Duration, - value: Arc>>, - _phantom: PhantomData, +/// Asynchronous Cached Credentials Provider +pub trait ProvideCachedCredentials: Send + Sync + std::fmt::Debug { + /// Returns a future that provides cached credentials. + fn provide_cached_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> + where + Self: 'a; } -impl Clone for ExpiringCache { - fn clone(&self) -> Self { - Self { - buffer_time: self.buffer_time, - value: self.value.clone(), - _phantom: Default::default(), - } +/// Credentials cache wrapper that may be shared +/// +/// Newtype wrapper around ProvideCachedCredentials that implements Clone using an internal +/// Arc. +#[derive(Clone, Debug)] +pub struct SharedCredentialsCache(Arc); + +impl SharedCredentialsCache { + /// Create a new `SharedCredentialsCache` from `ProvideCredentialsCache` + /// + /// The given `cache` will be wrapped in an internal `Arc`. If your + /// cache is already in an `Arc`, use `SharedCredentialsCache::from(cache)` instead. + pub fn new(provider: impl ProvideCachedCredentials + 'static) -> Self { + Self(Arc::new(provider)) } } -impl ExpiringCache -where - T: Clone, -{ - /// Creates `ExpiringCache` with the given `buffer_time`. - pub fn new(buffer_time: Duration) -> Self { - ExpiringCache { - buffer_time, - value: Arc::new(RwLock::new(OnceCell::new())), - _phantom: Default::default(), - } +impl AsRef for SharedCredentialsCache { + fn as_ref(&self) -> &(dyn ProvideCachedCredentials + 'static) { + self.0.as_ref() } +} - #[cfg(test)] - async fn get(&self) -> Option - where - T: Clone, - { - self.value - .read() - .await - .get() - .cloned() - .map(|(creds, _expiry)| creds) +impl From> for SharedCredentialsCache { + fn from(cache: Arc) -> Self { + SharedCredentialsCache(cache) } +} - /// Attempts to refresh the cached value with the given future. - /// If multiple threads attempt to refresh at the same time, one of them will win, - /// and the others will await that thread's result rather than multiple refreshes occurring. - /// The function given to acquire a value future, `f`, will not be called - /// if another thread is chosen to load the value. - pub async fn get_or_load(&self, f: F) -> Result +impl ProvideCachedCredentials for SharedCredentialsCache { + fn provide_cached_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> where - F: FnOnce() -> Fut, - Fut: Future>, + Self: 'a, { - let lock = self.value.read().await; - let future = lock.get_or_try_init(f); - future.await.map(|(value, _expiry)| value.clone()) - } - - /// If the value is expired, clears the cache. Otherwise, yields the current value. - pub async fn yield_or_clear_if_expired(&self, now: SystemTime) -> Option { - // Short-circuit if the value is not expired - if let Some((value, expiry)) = self.value.read().await.get() { - if !expired(*expiry, self.buffer_time, now) { - return Some(value.clone()); - } - } - - // Acquire a write lock to clear the cache, but then once the lock is acquired, - // check again that the value is not already cleared. If it has been cleared, - // then another thread is refreshing the cache by the time the write lock was acquired. - let mut lock = self.value.write().await; - if let Some((_value, expiration)) = lock.get() { - // Also check that we're clearing the expired value and not a value - // that has been refreshed by another thread. - if expired(*expiration, self.buffer_time, now) { - *lock = OnceCell::new(); - } - } - None + self.0.provide_cached_credentials() } } -fn expired(expiration: SystemTime, buffer_time: Duration, now: SystemTime) -> bool { - now >= (expiration - buffer_time) +#[derive(Clone, Debug)] +pub(crate) enum Inner { + Lazy(lazy_caching::Builder), } -#[cfg(test)] -mod tests { - use super::{expired, ExpiringCache}; - use crate::{provider::error::CredentialsError, Credentials}; - use std::time::{Duration, SystemTime}; - use tracing_test::traced_test; - - fn credentials(expired_secs: u64) -> Result<(Credentials, SystemTime), CredentialsError> { - let expiry = epoch_secs(expired_secs); - let creds = Credentials::new("test", "test", None, Some(expiry), "test"); - Ok((creds, expiry)) - } +/// `CredentialsCache` allows for configuring and creating a credentials cache. +/// +/// # Examples +/// +/// ```no_run +/// use aws_credential_types::Credentials; +/// use aws_credential_types::cache::CredentialsCache; +/// use aws_credential_types::credential_fn::provide_credentials_fn; +/// use std::sync::Arc; +/// +/// let credentials_cache = CredentialsCache::lazy_builder() +/// .into_credentials_cache() +/// .create_cache(Arc::new(provide_credentials_fn(|| async { +/// // An async process to retrieve credentials would go here: +/// Ok(Credentials::new( +/// "example", +/// "example", +/// None, +/// None, +/// "my_provider_name" +/// )) +/// }))); +/// ``` +#[derive(Clone, Debug)] +pub struct CredentialsCache { + pub(crate) inner: Inner, +} - fn epoch_secs(secs: u64) -> SystemTime { - SystemTime::UNIX_EPOCH + Duration::from_secs(secs) +impl CredentialsCache { + /// Creates a [`CredentialsCache`] from the default [`LazyBuilder`]. + pub fn lazy() -> Self { + Self::lazy_builder().into_credentials_cache() } - #[test] - fn expired_check() { - let ts = epoch_secs(100); - assert!(expired(ts, Duration::from_secs(10), epoch_secs(1000))); - assert!(expired(ts, Duration::from_secs(10), epoch_secs(90))); - assert!(!expired(ts, Duration::from_secs(10), epoch_secs(10))); + /// Returns the default [`LazyBuilder`]. + pub fn lazy_builder() -> LazyBuilder { + lazy_caching::Builder::new() } - #[traced_test] - #[tokio::test] - async fn cache_clears_if_expired_only() { - let cache = ExpiringCache::new(Duration::from_secs(10)); - assert!(cache - .yield_or_clear_if_expired(epoch_secs(100)) - .await - .is_none()); - - cache - .get_or_load(|| async { credentials(100) }) - .await - .unwrap(); - assert_eq!(Some(epoch_secs(100)), cache.get().await.unwrap().expiry()); - - // It should not clear the credentials if they're not expired - assert_eq!( - Some(epoch_secs(100)), - cache - .yield_or_clear_if_expired(epoch_secs(10)) - .await - .unwrap() - .expiry() - ); - - // It should clear the credentials if they're expired - assert!(cache - .yield_or_clear_if_expired(epoch_secs(500)) - .await - .is_none()); - assert!(cache.get().await.is_none()); + /// Creates a [`SharedCredentialsCache`] wrapping a concrete caching implementation. + pub fn create_cache(self, provider: Arc) -> SharedCredentialsCache { + match self.inner { + Inner::Lazy(builder) => SharedCredentialsCache::new(builder.build(provider)), + } } } diff --git a/aws/rust-runtime/aws-credential-types/src/cache/expiring_cache.rs b/aws/rust-runtime/aws-credential-types/src/cache/expiring_cache.rs new file mode 100644 index 0000000000000000000000000000000000000000..67556aad9b39daaa39c17eab88592d44db2bface --- /dev/null +++ b/aws/rust-runtime/aws-credential-types/src/cache/expiring_cache.rs @@ -0,0 +1,162 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use std::future::Future; +use std::marker::PhantomData; +use std::sync::Arc; +use std::time::{Duration, SystemTime}; +use tokio::sync::{OnceCell, RwLock}; + +/// Expiry-aware cache +/// +/// [`ExpiringCache`] implements two important features: +/// 1. Respect expiry of contents +/// 2. Deduplicate load requests to prevent thundering herds when no value is present. +#[derive(Debug)] +pub struct ExpiringCache { + /// Amount of time before the actual expiration time + /// when the value is considered expired. + buffer_time: Duration, + value: Arc>>, + _phantom: PhantomData, +} + +impl Clone for ExpiringCache { + fn clone(&self) -> Self { + Self { + buffer_time: self.buffer_time, + value: self.value.clone(), + _phantom: Default::default(), + } + } +} + +impl ExpiringCache +where + T: Clone, +{ + /// Creates `ExpiringCache` with the given `buffer_time`. + pub fn new(buffer_time: Duration) -> Self { + ExpiringCache { + buffer_time, + value: Arc::new(RwLock::new(OnceCell::new())), + _phantom: Default::default(), + } + } + + #[cfg(test)] + async fn get(&self) -> Option + where + T: Clone, + { + self.value + .read() + .await + .get() + .cloned() + .map(|(creds, _expiry)| creds) + } + + /// Attempts to refresh the cached value with the given future. + /// If multiple threads attempt to refresh at the same time, one of them will win, + /// and the others will await that thread's result rather than multiple refreshes occurring. + /// The function given to acquire a value future, `f`, will not be called + /// if another thread is chosen to load the value. + pub async fn get_or_load(&self, f: F) -> Result + where + F: FnOnce() -> Fut, + Fut: Future>, + { + let lock = self.value.read().await; + let future = lock.get_or_try_init(f); + future.await.map(|(value, _expiry)| value.clone()) + } + + /// If the value is expired, clears the cache. Otherwise, yields the current value. + pub async fn yield_or_clear_if_expired(&self, now: SystemTime) -> Option { + // Short-circuit if the value is not expired + if let Some((value, expiry)) = self.value.read().await.get() { + if !expired(*expiry, self.buffer_time, now) { + return Some(value.clone()); + } + } + + // Acquire a write lock to clear the cache, but then once the lock is acquired, + // check again that the value is not already cleared. If it has been cleared, + // then another thread is refreshing the cache by the time the write lock was acquired. + let mut lock = self.value.write().await; + if let Some((_value, expiration)) = lock.get() { + // Also check that we're clearing the expired value and not a value + // that has been refreshed by another thread. + if expired(*expiration, self.buffer_time, now) { + *lock = OnceCell::new(); + } + } + None + } +} + +fn expired(expiration: SystemTime, buffer_time: Duration, now: SystemTime) -> bool { + now >= (expiration - buffer_time) +} + +#[cfg(test)] +mod tests { + use super::{expired, ExpiringCache}; + use crate::{provider::error::CredentialsError, Credentials}; + use std::time::{Duration, SystemTime}; + use tracing_test::traced_test; + + fn credentials(expired_secs: u64) -> Result<(Credentials, SystemTime), CredentialsError> { + let expiry = epoch_secs(expired_secs); + let creds = Credentials::new("test", "test", None, Some(expiry), "test"); + Ok((creds, expiry)) + } + + fn epoch_secs(secs: u64) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_secs(secs) + } + + #[test] + fn expired_check() { + let ts = epoch_secs(100); + assert!(expired(ts, Duration::from_secs(10), epoch_secs(1000))); + assert!(expired(ts, Duration::from_secs(10), epoch_secs(90))); + assert!(!expired(ts, Duration::from_secs(10), epoch_secs(10))); + } + + #[traced_test] + #[tokio::test] + async fn cache_clears_if_expired_only() { + let cache = ExpiringCache::new(Duration::from_secs(10)); + assert!(cache + .yield_or_clear_if_expired(epoch_secs(100)) + .await + .is_none()); + + cache + .get_or_load(|| async { credentials(100) }) + .await + .unwrap(); + assert_eq!(Some(epoch_secs(100)), cache.get().await.unwrap().expiry()); + + // It should not clear the credentials if they're not expired + assert_eq!( + Some(epoch_secs(100)), + cache + .yield_or_clear_if_expired(epoch_secs(10)) + .await + .unwrap() + .expiry() + ); + + // It should clear the credentials if they're expired + assert!(cache + .yield_or_clear_if_expired(epoch_secs(500)) + .await + .is_none()); + assert!(cache.get().await.is_none()); + } +} diff --git a/aws/rust-runtime/aws-credential-types/src/lazy_caching.rs b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs similarity index 72% rename from aws/rust-runtime/aws-credential-types/src/lazy_caching.rs rename to aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs index cc2c6e662960b23c4ae8ddb6baddae1d74675bce..c448e862bdaf0dde42693c08fa2028feac3ae938 100644 --- a/aws/rust-runtime/aws-credential-types/src/lazy_caching.rs +++ b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Lazy, caching, credentials provider implementation +//! Lazy, credentials cache implementation use std::sync::Arc; use std::time::{Duration, Instant}; @@ -12,7 +12,7 @@ use aws_smithy_async::future::timeout::Timeout; use aws_smithy_async::rt::sleep::AsyncSleep; use tracing::{debug, info, info_span, Instrument}; -use crate::cache::ExpiringCache; +use crate::cache::{ExpiringCache, ProvideCachedCredentials}; use crate::provider::{error::CredentialsError, future, ProvideCredentials}; use crate::time_source::TimeSource; @@ -20,54 +20,43 @@ const DEFAULT_LOAD_TIMEOUT: Duration = Duration::from_secs(5); const DEFAULT_CREDENTIAL_EXPIRATION: Duration = Duration::from_secs(15 * 60); const DEFAULT_BUFFER_TIME: Duration = Duration::from_secs(10); -/// `LazyCachingCredentialsProvider` implements [`ProvideCredentials`] by caching -/// credentials that it loads by calling a user-provided [`ProvideCredentials`] implementation. -/// -/// For example, you can provide an [`ProvideCredentials`] implementation that calls -/// AWS STS's AssumeRole operation to get temporary credentials, and `LazyCachingCredentialsProvider` -/// will cache those credentials until they expire. #[derive(Debug)] -pub struct LazyCachingCredentialsProvider { +pub(crate) struct LazyCredentialsCache { time: TimeSource, sleeper: Arc, cache: ExpiringCache, - loader: Arc, + provider: Arc, load_timeout: Duration, default_credential_expiration: Duration, } -impl LazyCachingCredentialsProvider { +impl LazyCredentialsCache { fn new( time: TimeSource, sleeper: Arc, - loader: Arc, + provider: Arc, load_timeout: Duration, default_credential_expiration: Duration, buffer_time: Duration, ) -> Self { - LazyCachingCredentialsProvider { + Self { time, sleeper, cache: ExpiringCache::new(buffer_time), - loader, + provider, load_timeout, default_credential_expiration, } } - - /// Returns a new `Builder` that can be used to construct the `LazyCachingCredentialsProvider`. - pub fn builder() -> builder::Builder { - builder::Builder::new() - } } -impl ProvideCredentials for LazyCachingCredentialsProvider { - fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'_> +impl ProvideCachedCredentials for LazyCredentialsCache { + fn provide_cached_credentials<'a>(&'a self) -> future::ProvideCredentials<'_> where Self: 'a, { let now = self.time.now(); - let loader = self.loader.clone(); + let provider = self.provider.clone(); let timeout_future = self.sleeper.sleep(self.load_timeout); let load_timeout = self.load_timeout; let cache = self.cache.clone(); @@ -83,7 +72,7 @@ impl ProvideCredentials for LazyCachingCredentialsProvider { // There may be other threads also loading simultaneously, but this is OK // since the futures are not eagerly executed, and the cache will only run one // of them. - let future = Timeout::new(loader.provide_credentials(), timeout_future); + let future = Timeout::new(provider.provide_credentials(), timeout_future); let start_time = Instant::now(); let result = cache .get_or_load(|| { @@ -120,36 +109,32 @@ mod builder { use std::sync::Arc; use std::time::Duration; + use crate::cache::{CredentialsCache, Inner}; use crate::provider::ProvideCredentials; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep}; use super::TimeSource; use super::{ - LazyCachingCredentialsProvider, DEFAULT_BUFFER_TIME, DEFAULT_CREDENTIAL_EXPIRATION, + LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_LOAD_TIMEOUT, }; - /// Builder for constructing a [`LazyCachingCredentialsProvider`]. + /// Builder for constructing a `LazyCredentialsCache`. /// - /// # Examples + /// `LazyCredentialsCache` implements [`ProvideCachedCredentials`](crate::cache::ProvideCachedCredentials) by caching + /// credentials that it loads by calling a user-provided [`ProvideCredentials`] implementation. /// - /// ```no_run - /// use aws_credential_types::Credentials; - /// use aws_credential_types::credential_fn::provide_credentials_fn; - /// use aws_credential_types::lazy_caching::LazyCachingCredentialsProvider; + /// For example, you can provide a [`ProvideCredentials`] implementation that calls + /// AWS STS's AssumeRole operation to get temporary credentials, and `LazyCredentialsCache` + /// will cache those credentials until they expire. /// - /// let provider = LazyCachingCredentialsProvider::builder() - /// .load(provide_credentials_fn(|| async { - /// // An async process to retrieve credentials would go here: - /// Ok(Credentials::new("example", "example", None, None, "my_provider_name")) - /// })) - /// .build(); - /// ``` - #[derive(Debug, Default)] + /// Callers outside of this crate cannot call `build` directly. They can instead call + /// `into_credentials_cache` to obtain a [`CredentialsCache`]. Its `create_cache` then calls + /// `build` to create a `LazyCredentialsCache`. + #[derive(Clone, Debug, Default)] pub struct Builder { sleep: Option>, time_source: Option, - load: Option>, load_timeout: Option, buffer_time: Option, default_credential_expiration: Option, @@ -161,48 +146,35 @@ mod builder { Default::default() } - /// Override configuration for the [Builder] - pub fn configure( - mut self, - sleep: Option>, - time_source: TimeSource, - ) -> Self { - self.sleep = sleep; - self.time_source = Some(time_source); - 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 { - self.set_load(Some(loader)); - self - } - - /// An implementation of [`ProvideCredentials`] that will be used to load - /// the cached credentials once they're expired. - pub fn set_load(&mut self, loader: Option) -> &mut Self { - self.load = loader.map(|l| Arc::new(l) as Arc); - self - } - /// Implementation of [`AsyncSleep`] to use for timeouts. /// - /// This enables use of the `LazyCachingCredentialsProvider` with other async runtimes. + /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - pub fn sleep(mut self, sleep: impl AsyncSleep + 'static) -> Self { + pub fn sleep(mut self, sleep: Arc) -> Self { self.set_sleep(Some(sleep)); self } /// Implementation of [`AsyncSleep`] to use for timeouts. /// - /// This enables use of the `LazyCachingCredentialsProvider` with other async runtimes. + /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - pub fn set_sleep(&mut self, sleep: Option) -> &mut Self { - self.sleep = sleep.map(|s| Arc::new(s) as Arc); + pub fn set_sleep(&mut self, sleep: Option>) -> &mut Self { + self.sleep = sleep; + self + } + + #[doc(hidden)] // because they only exist for tests + pub fn time_source(mut self, time_source: TimeSource) -> Self { + self.set_time_source(Some(time_source)); + self + } + + #[doc(hidden)] // because they only exist for tests + pub fn set_time_source(&mut self, time_source: Option) -> &mut Self { + self.time_source = time_source; self } @@ -273,13 +245,20 @@ mod builder { self } - /// Creates the [`LazyCachingCredentialsProvider`]. + /// Converts [`Builder`] into [`CredentialsCache`]. + pub fn into_credentials_cache(self) -> CredentialsCache { + CredentialsCache { + inner: Inner::Lazy(self), + } + } + + /// Creates the [`LazyCredentialsCache`] with the passed-in `provider`. /// /// # Panics /// This will panic if no `sleep` implementation is given and if no default crate features /// are used. By default, the [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep) /// implementation will be set automatically. - pub fn build(self) -> LazyCachingCredentialsProvider { + pub(crate) fn build(self, provider: Arc) -> LazyCredentialsCache { let default_credential_expiration = self .default_credential_expiration .unwrap_or(DEFAULT_CREDENTIAL_EXPIRATION); @@ -287,12 +266,12 @@ mod builder { default_credential_expiration >= DEFAULT_CREDENTIAL_EXPIRATION, "default_credential_expiration must be at least 15 minutes" ); - LazyCachingCredentialsProvider::new( + LazyCredentialsCache::new( self.time_source.unwrap_or_default(), self.sleep.unwrap_or_else(|| { default_async_sleep().expect("no default sleep implementation available") }), - self.load.expect("load implementation is required"), + provider, self.load_timeout.unwrap_or(DEFAULT_LOAD_TIMEOUT), default_credential_expiration, self.buffer_time.unwrap_or(DEFAULT_BUFFER_TIME), @@ -311,23 +290,21 @@ mod tests { use tracing_test::traced_test; use crate::{ - credential_fn::provide_credentials_fn, - provider::{error::CredentialsError, ProvideCredentials}, - time_source::TestingTimeSource, - Credentials, + cache::ProvideCachedCredentials, credential_fn::provide_credentials_fn, + provider::error::CredentialsError, time_source::TestingTimeSource, Credentials, }; use super::{ - LazyCachingCredentialsProvider, TimeSource, DEFAULT_BUFFER_TIME, - DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_LOAD_TIMEOUT, + LazyCredentialsCache, TimeSource, DEFAULT_BUFFER_TIME, DEFAULT_CREDENTIAL_EXPIRATION, + DEFAULT_LOAD_TIMEOUT, }; fn test_provider( time: TimeSource, load_list: Vec, - ) -> LazyCachingCredentialsProvider { + ) -> LazyCredentialsCache { let load_list = Arc::new(Mutex::new(load_list)); - LazyCachingCredentialsProvider::new( + LazyCredentialsCache::new( time, Arc::new(TokioSleep::new()), Arc::new(provide_credentials_fn(move || { @@ -352,9 +329,9 @@ mod tests { Credentials::new("test", "test", None, Some(epoch_secs(expired_secs)), "test") } - async fn expect_creds(expired_secs: u64, provider: &LazyCachingCredentialsProvider) { + async fn expect_creds(expired_secs: u64, provider: &LazyCredentialsCache) { let creds = provider - .provide_credentials() + .provide_cached_credentials() .await .expect("expected credentials"); assert_eq!(Some(epoch_secs(expired_secs)), creds.expiry()); @@ -364,22 +341,22 @@ mod tests { #[tokio::test] async fn initial_populate_credentials() { let time = TestingTimeSource::new(UNIX_EPOCH); - let loader = Arc::new(provide_credentials_fn(|| async { + let provider = Arc::new(provide_credentials_fn(|| async { info!("refreshing the credentials"); Ok(credentials(1000)) })); - let provider = LazyCachingCredentialsProvider::new( + let credentials_cache = LazyCredentialsCache::new( TimeSource::testing(&time), Arc::new(TokioSleep::new()), - loader, + provider, DEFAULT_LOAD_TIMEOUT, DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_BUFFER_TIME, ); assert_eq!( epoch_secs(1000), - provider - .provide_credentials() + credentials_cache + .provide_cached_credentials() .await .unwrap() .expiry() @@ -391,7 +368,7 @@ mod tests { #[tokio::test] async fn reload_expired_credentials() { let mut time = TestingTimeSource::new(epoch_secs(100)); - let provider = test_provider( + let credentials_cache = test_provider( TimeSource::testing(&time), vec![ Ok(credentials(1000)), @@ -400,21 +377,21 @@ mod tests { ], ); - expect_creds(1000, &provider).await; - expect_creds(1000, &provider).await; + expect_creds(1000, &credentials_cache).await; + expect_creds(1000, &credentials_cache).await; time.set_time(epoch_secs(1500)); - expect_creds(2000, &provider).await; - expect_creds(2000, &provider).await; + expect_creds(2000, &credentials_cache).await; + expect_creds(2000, &credentials_cache).await; time.set_time(epoch_secs(2500)); - expect_creds(3000, &provider).await; - expect_creds(3000, &provider).await; + expect_creds(3000, &credentials_cache).await; + expect_creds(3000, &credentials_cache).await; } #[traced_test] #[tokio::test] async fn load_failed_error() { let mut time = TestingTimeSource::new(epoch_secs(100)); - let provider = test_provider( + let credentials_cache = test_provider( TimeSource::testing(&time), vec![ Ok(credentials(1000)), @@ -422,9 +399,12 @@ mod tests { ], ); - expect_creds(1000, &provider).await; + expect_creds(1000, &credentials_cache).await; time.set_time(epoch_secs(1500)); - assert!(provider.provide_credentials().await.is_err()); + assert!(credentials_cache + .provide_cached_credentials() + .await + .is_err()); } #[traced_test] @@ -437,7 +417,7 @@ mod tests { .unwrap(); let time = TestingTimeSource::new(epoch_secs(0)); - let provider = Arc::new(test_provider( + let credentials_cache = Arc::new(test_provider( TimeSource::testing(&time), vec![ Ok(credentials(500)), @@ -453,13 +433,16 @@ mod tests { for i in 0..4 { let mut tasks = Vec::new(); for j in 0..50 { - let provider = provider.clone(); + let credentials_cache = credentials_cache.clone(); let time = locked_time.clone(); tasks.push(rt.spawn(async move { let now = epoch_secs(i * 1000 + (4 * j)); time.lock().unwrap().set_time(now); - let creds = provider.provide_credentials().await.unwrap(); + let creds = credentials_cache + .provide_cached_credentials() + .await + .unwrap(); assert!( creds.expiry().unwrap() >= now, "{:?} >= {:?}", @@ -478,7 +461,7 @@ mod tests { #[traced_test] async fn load_timeout() { let time = TestingTimeSource::new(epoch_secs(100)); - let provider = LazyCachingCredentialsProvider::new( + let credentials_cache = LazyCredentialsCache::new( TimeSource::testing(&time), Arc::new(TokioSleep::new()), Arc::new(provide_credentials_fn(|| async { @@ -491,7 +474,7 @@ mod tests { ); assert!(matches!( - provider.provide_credentials().await, + credentials_cache.provide_cached_credentials().await, Err(CredentialsError::ProviderTimedOut { .. }) )); } diff --git a/aws/rust-runtime/aws-credential-types/src/lib.rs b/aws/rust-runtime/aws-credential-types/src/lib.rs index 01af142166e0ceb49914498ced070280af53f6b0..1f790c3fca5bb44b0a9653f3ab7a8772a7a5533c 100644 --- a/aws/rust-runtime/aws-credential-types/src/lib.rs +++ b/aws/rust-runtime/aws-credential-types/src/lib.rs @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! `aws-credential-types` provides the items concerned with AWS SDK credentials including: -//! * A trait for credentials providers +//! `aws-credential-types` provides types concerned with AWS SDK credentials including: +//! * Traits for credentials providers and for credentials caching //! * An opaque struct representing credentials //! * Concrete implementations of credentials caching @@ -19,7 +19,6 @@ pub mod cache; pub mod credential_fn; mod credentials_impl; -pub mod lazy_caching; pub mod provider; #[doc(hidden)] pub mod time_source; diff --git a/aws/rust-runtime/aws-credential-types/src/provider.rs b/aws/rust-runtime/aws-credential-types/src/provider.rs index 7f0e6282f1421c0f58bfc9cd639018a584103514..497887597cfe577dfaeae96f53de4e19c0e1edd0 100644 --- a/aws/rust-runtime/aws-credential-types/src/provider.rs +++ b/aws/rust-runtime/aws-credential-types/src/provider.rs @@ -299,41 +299,3 @@ impl ProvideCredentials for Arc { self.as_ref().provide_credentials() } } - -/// Credentials Provider wrapper that may be shared -/// -/// Newtype wrapper around ProvideCredentials that implements Clone using an internal -/// Arc. -#[derive(Clone, Debug)] -pub struct SharedCredentialsProvider(Arc); - -impl SharedCredentialsProvider { - /// Create a new SharedCredentials provider from `ProvideCredentials` - /// - /// The given provider will be wrapped in an internal `Arc`. If your - /// provider is already in an `Arc`, use `SharedCredentialsProvider::from(provider)` instead. - pub fn new(provider: impl ProvideCredentials + 'static) -> Self { - Self(Arc::new(provider)) - } -} - -impl AsRef for SharedCredentialsProvider { - fn as_ref(&self) -> &(dyn ProvideCredentials + 'static) { - self.0.as_ref() - } -} - -impl From> for SharedCredentialsProvider { - fn from(provider: Arc) -> Self { - SharedCredentialsProvider(provider) - } -} - -impl ProvideCredentials for SharedCredentialsProvider { - fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> - where - Self: 'a, - { - self.0.provide_credentials() - } -} diff --git a/aws/rust-runtime/aws-http/external-types.toml b/aws/rust-runtime/aws-http/external-types.toml index 4e648122ce46b686f9516c6d3d60a0ff2996f418..33a2ed14aa6d9fa9c82f46f2603eb99aeceee032 100644 --- a/aws/rust-runtime/aws-http/external-types.toml +++ b/aws/rust-runtime/aws-http/external-types.toml @@ -1,5 +1,5 @@ allowed_external_types = [ - "aws_credential_types::provider::*", + "aws_credential_types::*", "aws_smithy_http::*", "aws_smithy_types::*", "aws_types::*", diff --git a/aws/rust-runtime/aws-http/src/auth.rs b/aws/rust-runtime/aws-http/src/auth.rs index 8389bb9e4ec9fb72462cec6365d686c18b2fed13..97d943f1d828ce8d726719248191c507c4591b8f 100644 --- a/aws/rust-runtime/aws-http/src/auth.rs +++ b/aws/rust-runtime/aws-http/src/auth.rs @@ -3,27 +3,26 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_credential_types::provider::{ - error::CredentialsError, ProvideCredentials, SharedCredentialsProvider, -}; +use aws_credential_types::cache::{ProvideCachedCredentials, SharedCredentialsCache}; +use aws_credential_types::provider::error::CredentialsError; use aws_smithy_http::middleware::AsyncMapRequest; use aws_smithy_http::operation::Request; use aws_smithy_http::property_bag::PropertyBag; use std::future::Future; use std::pin::Pin; -/// Sets the credentials provider in the given property bag. -pub fn set_provider(bag: &mut PropertyBag, provider: SharedCredentialsProvider) { - bag.insert(provider); +/// Sets the credentials cache in the given property bag. +pub fn set_credentials_cache(bag: &mut PropertyBag, cache: SharedCredentialsCache) { + bag.insert(cache); } -/// Middleware stage that loads credentials from a [CredentialsProvider](aws_credential_types::provider::ProvideCredentials) +/// Middleware stage that loads credentials from a [SharedCredentialsCache](aws_credential_types::cache::SharedCredentialsCache) /// and places them in the property bag of the request. /// /// [CredentialsStage] implements [`AsyncMapRequest`](aws_smithy_http::middleware::AsyncMapRequest), and: -/// 1. Retrieves a `CredentialsProvider` from the property bag. -/// 2. Calls the credential provider's `provide_credentials` and awaits its result. -/// 3. Places returned `Credentials` into the property bad to drive downstream signing middleware. +/// 1. Retrieves a `SharedCredentialsCache` from the property bag. +/// 2. Calls the credential cache's `provide_cached_credentials` and awaits its result. +/// 3. Places returned `Credentials` into the property bag to drive downstream signing middleware. #[derive(Clone, Debug, Default)] #[non_exhaustive] pub struct CredentialsStage; @@ -35,24 +34,24 @@ impl CredentialsStage { } async fn load_creds(mut request: Request) -> Result { - let provider = request + let credentials_cache = request .properties() - .get::() + .get::() .cloned(); - let provider = match provider { - Some(provider) => provider, + let credentials_cache = match credentials_cache { + Some(credentials_cache) => credentials_cache, None => { - tracing::info!("no credentials provider for request"); + tracing::info!("no credentials cache for request"); return Ok(request); } }; - match provider.provide_credentials().await { + match credentials_cache.provide_cached_credentials().await { Ok(creds) => { request.properties_mut().insert(creds); } - // ignore the case where there is no provider wired up + // ignore the case where there is no credentials cache wired up Err(CredentialsError::CredentialsNotLoaded { .. }) => { - tracing::info!("provider returned CredentialsNotLoaded, ignoring") + tracing::info!("credentials cache returned CredentialsNotLoaded, ignoring") } // if we get another error class, there is probably something actually wrong that the user will // want to know about @@ -81,10 +80,7 @@ mod error { impl fmt::Display for CredentialsStageError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "failed to load credentials from the credentials provider" - ) + write!(f, "failed to load credentials from the credentials cache") } } @@ -114,22 +110,23 @@ impl AsyncMapRequest for CredentialsStage { #[cfg(test)] mod tests { - use super::set_provider; + use super::set_credentials_cache; use super::CredentialsStage; - use aws_credential_types::{ - provider::{ - error::CredentialsError, future, ProvideCredentials, SharedCredentialsProvider, - }, - Credentials, + use aws_credential_types::cache::{ + CredentialsCache, ProvideCachedCredentials, SharedCredentialsCache, }; + use aws_credential_types::credential_fn::provide_credentials_fn; + use aws_credential_types::provider::{error::CredentialsError, future}; + use aws_credential_types::Credentials; use aws_smithy_http::body::SdkBody; use aws_smithy_http::middleware::AsyncMapRequest; use aws_smithy_http::operation; + use std::sync::Arc; #[derive(Debug)] struct Unhandled; - impl ProvideCredentials for Unhandled { - fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> + impl ProvideCachedCredentials for Unhandled { + fn provide_cached_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> where Self: 'a, { @@ -139,8 +136,8 @@ mod tests { #[derive(Debug)] struct NoCreds; - impl ProvideCredentials for NoCreds { - fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> + impl ProvideCachedCredentials for NoCreds { + fn provide_cached_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> where Self: 'a, { @@ -149,33 +146,33 @@ mod tests { } #[tokio::test] - async fn no_cred_provider_is_ok() { + async fn no_credential_cache_is_ok() { let req = operation::Request::new(http::Request::new(SdkBody::from("some body"))); CredentialsStage::new() .apply(req) .await - .expect("no credential provider should not populate credentials"); + .expect("no credentials cache should not populate credentials"); } #[tokio::test] - async fn provider_failure_is_failure() { + async fn credentials_cache_failure_is_failure() { let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body"))); - set_provider( + set_credentials_cache( &mut req.properties_mut(), - SharedCredentialsProvider::new(Unhandled), + SharedCredentialsCache::new(Unhandled), ); CredentialsStage::new() .apply(req) .await - .expect_err("no credential provider should not populate credentials"); + .expect_err("no credentials cache should not populate credentials"); } #[tokio::test] async fn credentials_not_loaded_is_ok() { let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body"))); - set_provider( + set_credentials_cache( &mut req.properties_mut(), - SharedCredentialsProvider::new(NoCreds), + SharedCredentialsCache::new(NoCreds), ); CredentialsStage::new() .apply(req) @@ -186,14 +183,19 @@ mod tests { #[tokio::test] async fn async_map_request_apply_populates_credentials() { let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body"))); - set_provider( + let credentials_cache = CredentialsCache::lazy_builder() + .into_credentials_cache() + .create_cache(Arc::new(provide_credentials_fn(|| async { + Ok(Credentials::for_tests()) + }))); + set_credentials_cache( &mut req.properties_mut(), - SharedCredentialsProvider::new(Credentials::for_tests()), + SharedCredentialsCache::from(credentials_cache), ); let req = CredentialsStage::new() .apply(req) .await - .expect("credential provider is in the bag; should succeed"); + .expect("credentials cache is in the bag; should succeed"); assert!( req.properties().get::().is_some(), "it should set credentials on the request config" diff --git a/aws/rust-runtime/aws-inlineable/tests/middleware_e2e_test.rs b/aws/rust-runtime/aws-inlineable/tests/middleware_e2e_test.rs index cb7f4e03993e13a282977f6afa523b4bd8b1fbd6..04fd7ca61ab2bfe045b321b96457629de7400df2 100644 --- a/aws/rust-runtime/aws-inlineable/tests/middleware_e2e_test.rs +++ b/aws/rust-runtime/aws-inlineable/tests/middleware_e2e_test.rs @@ -7,8 +7,11 @@ use std::convert::Infallible; use std::error::Error; use std::fmt; use std::fmt::{Display, Formatter}; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; +use aws_credential_types::cache::CredentialsCache; +use aws_credential_types::Credentials; use aws_smithy_client::erase::DynConnector; use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; @@ -21,8 +24,6 @@ use bytes::Bytes; use http::header::{AUTHORIZATION, USER_AGENT}; use http::{self, Uri}; -use aws_credential_types::provider::SharedCredentialsProvider; -use aws_credential_types::Credentials; use aws_http::retry::AwsResponseRetryClassifier; use aws_http::user_agent::AwsUserAgent; use aws_inlineable::middleware::DefaultMiddleware; @@ -85,9 +86,9 @@ fn test_operation() -> Operation, - credentials_provider: Option, + credentials_cache: Option, + credentials_provider: Option>, region: Option, endpoint_resolver: Option>, endpoint_url: Option, @@ -67,7 +69,8 @@ pub struct SdkConfig { #[derive(Debug, Default)] pub struct Builder { app_name: Option, - credentials_provider: Option, + credentials_cache: Option, + credentials_provider: Option>, region: Option, endpoint_resolver: Option>, endpoint_url: Option, @@ -338,12 +341,50 @@ impl Builder { self } + /// Set the [`CredentialsCache`] for the builder + /// + /// # Examples + /// ```rust + /// use aws_credential_types::cache::CredentialsCache; + /// use aws_types::SdkConfig; + /// let config = SdkConfig::builder() + /// .credentials_cache(CredentialsCache::lazy()) + /// .build(); + /// ``` + pub fn credentials_cache(mut self, cache: CredentialsCache) -> Self { + self.set_credentials_cache(Some(cache)); + self + } + + /// Set the [`CredentialsCache`] for the builder + /// + /// # Examples + /// ```rust + /// use aws_credential_types::cache::CredentialsCache; + /// use aws_types::SdkConfig; + /// fn override_credentials_cache() -> bool { + /// // ... + /// # true + /// } + /// + /// let mut builder = SdkConfig::builder(); + /// if override_credentials_cache() { + /// builder.set_credentials_cache(Some(CredentialsCache::lazy())); + /// } + /// let config = builder.build(); + /// ``` + pub fn set_credentials_cache(&mut self, cache: Option) -> &mut Self { + self.credentials_cache = cache; + self + } + /// Set the credentials provider for the builder /// /// # Examples /// ```rust - /// use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; + /// use aws_credential_types::provider::ProvideCredentials; /// use aws_types::SdkConfig; + /// use std::sync::Arc; /// fn make_provider() -> impl ProvideCredentials { /// // ... /// # use aws_credential_types::Credentials; @@ -351,10 +392,10 @@ impl Builder { /// } /// /// let config = SdkConfig::builder() - /// .credentials_provider(SharedCredentialsProvider::new(make_provider())) + /// .credentials_provider(Arc::new(make_provider())) /// .build(); /// ``` - pub fn credentials_provider(mut self, provider: SharedCredentialsProvider) -> Self { + pub fn credentials_provider(mut self, provider: Arc) -> Self { self.set_credentials_provider(Some(provider)); self } @@ -363,8 +404,9 @@ impl Builder { /// /// # Examples /// ```rust - /// use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; + /// use aws_credential_types::provider::ProvideCredentials; /// use aws_types::SdkConfig; + /// use std::sync::Arc; /// fn make_provider() -> impl ProvideCredentials { /// // ... /// # use aws_credential_types::Credentials; @@ -378,13 +420,13 @@ impl Builder { /// /// let mut builder = SdkConfig::builder(); /// if override_provider() { - /// builder.set_credentials_provider(Some(SharedCredentialsProvider::new(make_provider()))); + /// builder.set_credentials_provider(Some(Arc::new(make_provider()))); /// } /// let config = builder.build(); /// ``` pub fn set_credentials_provider( &mut self, - provider: Option, + provider: Option>, ) -> &mut Self { self.credentials_provider = provider; self @@ -514,6 +556,7 @@ impl Builder { pub fn build(self) -> SdkConfig { SdkConfig { app_name: self.app_name, + credentials_cache: self.credentials_cache, credentials_provider: self.credentials_provider, region: self.region, endpoint_resolver: self.endpoint_resolver, @@ -560,9 +603,14 @@ impl SdkConfig { self.sleep_impl.clone() } + /// Configured credentials cache + pub fn credentials_cache(&self) -> Option<&CredentialsCache> { + self.credentials_cache.as_ref() + } + /// Configured credentials provider - pub fn credentials_provider(&self) -> Option<&SharedCredentialsProvider> { - self.credentials_provider.as_ref() + pub fn credentials_provider(&self) -> Option> { + self.credentials_provider.clone() } /// Configured app name diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index 37f3ce94ce5faebe4f427ddecb5852fcb637f07c..b1b4a862968c55f114fae491d6c69909306eeb96 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -20,6 +20,7 @@ import software.amazon.smithy.rustsdk.customize.sts.STSDecorator val DECORATORS: List = listOf( // General AWS Decorators + CredentialsCacheDecorator(), CredentialsProviderDecorator(), RegionDecorator(), AwsEndpointDecorator(), diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt new file mode 100644 index 0000000000000000000000000000000000000000..15e49945e360a55af728c130a50a7bef3fa1cb59 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt @@ -0,0 +1,146 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig +import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocSection +import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization +import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection +import software.amazon.smithy.rust.codegen.core.smithy.customize.Section + +class CredentialsCacheDecorator : ClientCodegenDecorator { + override val name: String = "CredentialsCache" + override val order: Byte = 0 + override fun configCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List { + return baseCustomizations + CredentialCacheConfig(codegenContext.runtimeConfig) + } + + override fun operationCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List { + return baseCustomizations + CredentialsCacheFeature(codegenContext.runtimeConfig) + } + + override fun extraSections(codegenContext: ClientCodegenContext): List, (Section) -> Writable>> = + listOf( + SdkConfigSection.create { section -> + writable { + rust("${section.serviceConfigBuilder}.set_credentials_cache(${section.sdkConfig}.credentials_cache().cloned());") + } + }, + ) +} + +/** + * Add a `.credentials_cache` field and builder to the `Config` for a given service + */ +class CredentialCacheConfig(runtimeConfig: RuntimeConfig) : ConfigCustomization() { + private val codegenScope = arrayOf( + "cache" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("cache"), + "DefaultProvider" to defaultProvider(), + ) + + override fun section(section: ServiceConfig) = writable { + when (section) { + ServiceConfig.ConfigStruct -> rustTemplate( + """pub(crate) credentials_cache: #{cache}::SharedCredentialsCache,""", + *codegenScope, + ) + + ServiceConfig.ConfigImpl -> rustTemplate( + """ + /// Returns the credentials cache. + pub fn credentials_cache(&self) -> #{cache}::SharedCredentialsCache { + self.credentials_cache.clone() + } + """, + *codegenScope, + ) + + ServiceConfig.BuilderStruct -> + rustTemplate("credentials_cache: Option<#{cache}::CredentialsCache>,", *codegenScope) + + ServiceConfig.BuilderImpl -> { + rustTemplate( + """ + /// Sets the credentials cache for this service + pub fn credentials_cache(mut self, credentials_cache: #{cache}::CredentialsCache) -> Self { + self.set_credentials_cache(Some(credentials_cache)); + self + } + + /// Sets the credentials cache for this service + pub fn set_credentials_cache(&mut self, credentials_cache: Option<#{cache}::CredentialsCache>) -> &mut Self { + self.credentials_cache = credentials_cache; + self + } + """, + *codegenScope, + ) + } + + ServiceConfig.BuilderBuild -> rustTemplate( + """ + credentials_cache: self + .credentials_cache + .unwrap_or_else({ + let sleep = self.sleep_impl.clone(); + || match sleep { + Some(sleep) => { + #{cache}::CredentialsCache::lazy_builder() + .sleep(sleep) + .into_credentials_cache() + } + None => #{cache}::CredentialsCache::lazy(), + } + }) + .create_cache( + self.credentials_provider.unwrap_or_else(|| { + std::sync::Arc::new(#{DefaultProvider}) + }) + ), + """, + *codegenScope, + ) + + else -> emptySection + } + } +} + +class CredentialsCacheFeature(private val runtimeConfig: RuntimeConfig) : OperationCustomization() { + override fun section(section: OperationSection): Writable { + return when (section) { + is OperationSection.MutateRequest -> writable { + rust( + """ + #T(&mut ${section.request}.properties_mut(), ${section.config}.credentials_cache.clone()); + """, + setCredentialsCache(runtimeConfig), + ) + } + + else -> emptySection + } + } +} + +fun setCredentialsCache(runtimeConfig: RuntimeConfig) = + AwsRuntimeType.awsHttp(runtimeConfig).resolve("auth::set_credentials_cache") diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt index 001b35b8348c90c617f70cfdb50157cf0619143a..b584b14c6ef12f1bacf3424e2177e18e4aed53ed 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt @@ -5,7 +5,6 @@ package software.amazon.smithy.rustsdk -import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization @@ -17,8 +16,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocSection -import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization -import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection import software.amazon.smithy.rust.codegen.core.smithy.customize.Section import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection @@ -34,14 +31,6 @@ class CredentialsProviderDecorator : ClientCodegenDecorator { return baseCustomizations + CredentialProviderConfig(codegenContext.runtimeConfig) } - override fun operationCustomizations( - codegenContext: ClientCodegenContext, - operation: OperationShape, - baseCustomizations: List, - ): List { - return baseCustomizations + CredentialsProviderFeature(codegenContext.runtimeConfig) - } - override fun libRsCustomizations( codegenContext: ClientCodegenContext, baseCustomizations: List, @@ -53,7 +42,7 @@ class CredentialsProviderDecorator : ClientCodegenDecorator { listOf( SdkConfigSection.create { section -> writable { - rust("${section.serviceConfigBuilder}.set_credentials_provider(${section.sdkConfig}.credentials_provider().cloned());") + rust("${section.serviceConfigBuilder}.set_credentials_provider(${section.sdkConfig}.credentials_provider().clone());") } }, ) @@ -63,43 +52,26 @@ class CredentialsProviderDecorator : ClientCodegenDecorator { * Add a `.credentials_provider` field and builder to the `Config` for a given service */ class CredentialProviderConfig(runtimeConfig: RuntimeConfig) : ConfigCustomization() { - private val defaultProvider = defaultProvider() private val codegenScope = arrayOf( "provider" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("provider"), - "DefaultProvider" to defaultProvider, + "DefaultProvider" to defaultProvider(), ) override fun section(section: ServiceConfig) = writable { when (section) { - ServiceConfig.ConfigStruct -> rustTemplate( - """pub(crate) credentials_provider: #{provider}::SharedCredentialsProvider,""", - *codegenScope, - ) - - ServiceConfig.ConfigImpl -> rustTemplate( - """ - /// Returns the credentials provider. - pub fn credentials_provider(&self) -> #{provider}::SharedCredentialsProvider { - self.credentials_provider.clone() - } - """, - *codegenScope, - ) - ServiceConfig.BuilderStruct -> - rustTemplate("credentials_provider: Option<#{provider}::SharedCredentialsProvider>,", *codegenScope) - + rustTemplate("credentials_provider: Option>,", *codegenScope) ServiceConfig.BuilderImpl -> { rustTemplate( """ /// Sets the credentials provider for this service pub fn credentials_provider(mut self, credentials_provider: impl #{provider}::ProvideCredentials + 'static) -> Self { - self.credentials_provider = Some(#{provider}::SharedCredentialsProvider::new(credentials_provider)); + self.set_credentials_provider(Some(std::sync::Arc::new(credentials_provider))); self } /// Sets the credentials provider for this service - pub fn set_credentials_provider(&mut self, credentials_provider: Option<#{provider}::SharedCredentialsProvider>) -> &mut Self { + pub fn set_credentials_provider(&mut self, credentials_provider: Option>) -> &mut Self { self.credentials_provider = credentials_provider; self } @@ -108,28 +80,6 @@ class CredentialProviderConfig(runtimeConfig: RuntimeConfig) : ConfigCustomizati ) } - ServiceConfig.BuilderBuild -> rustTemplate( - "credentials_provider: self.credentials_provider.unwrap_or_else(|| #{provider}::SharedCredentialsProvider::new(#{DefaultProvider})),", - *codegenScope, - ) - - else -> emptySection - } - } -} - -class CredentialsProviderFeature(private val runtimeConfig: RuntimeConfig) : OperationCustomization() { - override fun section(section: OperationSection): Writable { - return when (section) { - is OperationSection.MutateRequest -> writable { - rust( - """ - #T(&mut ${section.request}.properties_mut(), ${section.config}.credentials_provider.clone()); - """, - setProvider(runtimeConfig), - ) - } - else -> emptySection } } @@ -152,5 +102,3 @@ class PubUseCredentials(private val runtimeConfig: RuntimeConfig) : LibRsCustomi fun defaultProvider() = RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("no_credentials")).resolve("NoCredentials") - -fun setProvider(runtimeConfig: RuntimeConfig) = AwsRuntimeType.awsHttp(runtimeConfig).resolve("auth::set_provider") diff --git a/aws/sdk/integration-tests/dynamodb/tests/cloning.rs b/aws/sdk/integration-tests/dynamodb/tests/cloning.rs index 4fd3c500fa9af3044496dd884a44a25c0c4021e2..c48a6d3bf774108a609c4ea2ad05fecf0c589ba1 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/cloning.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/cloning.rs @@ -3,16 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_types::region::Region; +use std::sync::Arc; // compiling this function validates that fluent builders are cloneable #[allow(dead_code)] async fn ensure_builders_clone() { let shared_config = aws_types::SdkConfig::builder() .region(Region::new("us-east-4")) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .build(); let client = aws_sdk_dynamodb::Client::new(&shared_config); let base_query = client.list_tables(); diff --git a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs index 5695b403d8fad1669ab46469893c3322bb094bbc..683afe3aa9cbd4b6d0ab2ea8c2c5aee2f0be7dd5 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use std::time::Duration; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_sdk_dynamodb::types::SdkError; use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep}; @@ -30,7 +29,7 @@ async fn api_call_timeout_retries() { let conf = SdkConfig::builder() .region(Region::new("us-east-2")) .http_connector(conn.clone()) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .timeout_config( TimeoutConfig::builder() .operation_attempt_timeout(Duration::new(123, 0)) @@ -63,7 +62,7 @@ async fn no_retries_on_operation_timeout() { let conf = SdkConfig::builder() .region(Region::new("us-east-2")) .http_connector(conn.clone()) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .timeout_config( TimeoutConfig::builder() .operation_timeout(Duration::new(123, 0)) diff --git a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs index 03c058f4dac242cdf9bd29d0f0757c79cf7833df..df67026e9e3bcc3b173d811f7a7c098885ccf7c6 100644 --- a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs +++ b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs @@ -124,23 +124,18 @@ async fn timeout_test(sleep_impl: Arc) -> Result<(), Box) -> Result<(), Box> { let conn = NeverConnector::new(); - let conf = - aws_types::SdkConfig::builder() - .region(Region::new("us-east-2")) - .http_connector(conn.clone()) - .credentials_provider( - aws_credential_types::provider::SharedCredentialsProvider::new( - Credentials::for_tests(), - ), - ) - .retry_config(RetryConfig::standard()) - .timeout_config( - TimeoutConfig::builder() - .operation_attempt_timeout(Duration::from_secs_f64(0.1)) - .build(), - ) - .sleep_impl(sleep_impl) - .build(); + let conf = aws_types::SdkConfig::builder() + .region(Region::new("us-east-2")) + .http_connector(conn.clone()) + .credentials_provider(Arc::new(Credentials::for_tests())) + .retry_config(RetryConfig::standard()) + .timeout_config( + TimeoutConfig::builder() + .operation_attempt_timeout(Duration::from_secs_f64(0.1)) + .build(), + ) + .sleep_impl(sleep_impl) + .build(); let client = Client::new(&conf); let resp = client .list_buckets() diff --git a/aws/sdk/integration-tests/s3/tests/checksums.rs b/aws/sdk/integration-tests/s3/tests/checksums.rs index 79f00a2a3a46cff3915d8c128aa7daad8b23267a..78a52c3543fec9fa27f53e5a3ca7ec9cd6059b09 100644 --- a/aws/sdk/integration-tests/s3/tests/checksums.rs +++ b/aws/sdk/integration-tests/s3/tests/checksums.rs @@ -4,7 +4,6 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::{model::ChecksumAlgorithm, output::GetObjectOutput, Client, Credentials, Region}; use aws_smithy_client::test_connection::{capture_request, TestConnection}; @@ -13,6 +12,7 @@ use http::header::AUTHORIZATION; use http::{HeaderValue, Uri}; use std::{ convert::Infallible, + sync::Arc, time::{Duration, UNIX_EPOCH}, }; @@ -58,7 +58,7 @@ async fn test_checksum_on_streaming_response( checksum_header_value, ); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); @@ -157,7 +157,7 @@ async fn test_checksum_on_streaming_request<'a>( ) { let (conn, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/concurrency.rs b/aws/sdk/integration-tests/s3/tests/concurrency.rs index c54d303d4bf46f1acb3df6811d4561c15888f897..29d9c8ae4ee2dd37f63f4fda056011ba105950da 100644 --- a/aws/sdk/integration-tests/s3/tests/concurrency.rs +++ b/aws/sdk/integration-tests/s3/tests/concurrency.rs @@ -8,7 +8,6 @@ use std::iter::repeat_with; use std::net::SocketAddr; use std::sync::Arc; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_sdk_s3::Client; use aws_smithy_types::timeout::TimeoutConfig; @@ -43,7 +42,7 @@ async fn test_concurrency_on_multi_thread_against_dummy_server() { let (server, server_addr) = start_agreeable_server().await; let _ = tokio::spawn(server); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .endpoint_url(format!("http://{server_addr}")) .build(); @@ -56,7 +55,7 @@ async fn test_concurrency_on_single_thread_against_dummy_server() { let (server, server_addr) = start_agreeable_server().await; let _ = tokio::spawn(server); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .endpoint_url(format!("http://{server_addr}")) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/customizable-operation.rs b/aws/sdk/integration-tests/s3/tests/customizable-operation.rs index f0b584aa12b44ab7cb6b6d8a82d2630912126f0a..199b5edbb97eec849ea63f1ae57c4014fadf91df 100644 --- a/aws/sdk/integration-tests/s3/tests/customizable-operation.rs +++ b/aws/sdk/integration-tests/s3/tests/customizable-operation.rs @@ -4,19 +4,19 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::test_connection::capture_request; use std::convert::Infallible; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_s3_ops_are_customizable() { let (conn, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/endpoints.rs b/aws/sdk/integration-tests/s3/tests/endpoints.rs index 93c37f5c87259e405a766680065c1a0dc58f26fc..d0a9f3501e2396dbf4d88a7ce982f89ec424139d 100644 --- a/aws/sdk/integration-tests/s3/tests/endpoints.rs +++ b/aws/sdk/integration-tests/s3/tests/endpoints.rs @@ -4,17 +4,17 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::Builder; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::test_connection::{capture_request, CaptureRequestReceiver}; use std::convert::Infallible; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; fn test_client(update_builder: fn(Builder) -> Builder) -> (CaptureRequestReceiver, Client) { let (conn, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-west-4")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs index e2f850cb68bb4f8672d5db7c1a2a676a2ad9371a..09e31930bf046dfbb1df1f2425fd1eedc84b593e 100644 --- a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs +++ b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::{model::ObjectAttributes, Client, Credentials, Region}; use aws_smithy_client::test_connection::TestConnection; @@ -12,6 +11,7 @@ use aws_types::SdkConfig; use http::header::AUTHORIZATION; use std::{ convert::Infallible, + sync::Arc, time::{Duration, UNIX_EPOCH}, }; @@ -46,7 +46,7 @@ async fn ignore_invalid_xml_body_root() { ]); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs index fd537a539a075de93920bbf8497d36a17fa980e1..d00ce6403639c925b747a023ac5bf6313e8c9f5b 100644 --- a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs +++ b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::{types::ByteStream, Client, Credentials, Region}; use aws_smithy_client::test_connection::capture_request; @@ -11,6 +10,7 @@ use aws_types::SdkConfig; use http::HeaderValue; use std::{ convert::Infallible, + sync::Arc, time::{Duration, UNIX_EPOCH}, }; @@ -54,7 +54,7 @@ const NAUGHTY_STRINGS: &str = include_str!("blns/blns.txt"); async fn test_s3_signer_with_naughty_string_metadata() { let (conn, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs index d039064cbc0719e4a4edeb72c976acd799bcb0ab..c4e73812fcb1e1b7e52cce17757ffcc4f026f087 100644 --- a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs +++ b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs @@ -4,19 +4,19 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::types::ByteStream; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::test_connection::capture_request; use std::convert::Infallible; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_operation_should_not_normalize_uri_path() { let (conn, rx) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs index 858d0138410d0b3bc9e4dfe0b61191f77dbb9528..381d55544d7f98956e022413aae1a1e98f1f783f 100644 --- a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs +++ b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs @@ -4,18 +4,18 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::test_connection::capture_request; use std::convert::Infallible; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_s3_signer_query_string_with_all_valid_chars() { let (conn, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/recursion-detection.rs b/aws/sdk/integration-tests/s3/tests/recursion-detection.rs index c4d739447523588380db060c6ffdf8cbb1ed9fbc..fb3486444b092cb3565ecc4de860424eda278b9e 100644 --- a/aws/sdk/integration-tests/s3/tests/recursion-detection.rs +++ b/aws/sdk/integration-tests/s3/tests/recursion-detection.rs @@ -4,10 +4,10 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::test_connection::capture_request; use http::HeaderValue; +use std::sync::Arc; #[tokio::test] async fn recursion_detection_applied() { @@ -15,7 +15,7 @@ async fn recursion_detection_applied() { std::env::set_var("_X_AMZN_TRACE_ID", "traceid"); let (conn, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/select-object-content.rs b/aws/sdk/integration-tests/s3/tests/select-object-content.rs index d68af2f68bbe19220d64f07d6cc798f9ec0672d3..ec22dfdb1f856c8267b9f38a3f39108f040860a8 100644 --- a/aws/sdk/integration-tests/s3/tests/select-object-content.rs +++ b/aws/sdk/integration-tests/s3/tests/select-object-content.rs @@ -4,7 +4,6 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::model::{ CompressionType, CsvInput, CsvOutput, ExpressionType, FileHeaderInfo, InputSerialization, OutputSerialization, SelectObjectContentEventStream, @@ -13,6 +12,7 @@ use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::dvr::{Event, ReplayingConnection}; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; use std::error::Error; +use std::sync::Arc; #[tokio::test] async fn test_success() { @@ -21,7 +21,7 @@ async fn test_success() { let replayer = ReplayingConnection::new(events); let sdk_config = SdkConfig::builder() .region(Region::from_static("us-east-2")) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .http_connector(replayer.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/signing-it.rs b/aws/sdk/integration-tests/s3/tests/signing-it.rs index 0efdc9a5f658c3efd8cd0788f821cae5cc7ba8de..d9b2e56a1421bc1372d7270f74faef23c93ba0ae 100644 --- a/aws/sdk/integration-tests/s3/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3/tests/signing-it.rs @@ -4,12 +4,12 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; use std::convert::Infallible; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] @@ -23,7 +23,7 @@ async fn test_signer() { http::Response::builder().status(200).body("").unwrap(), )]); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/streaming-response.rs b/aws/sdk/integration-tests/s3/tests/streaming-response.rs index 8ff6d6eb377c8ef6b323ad8d0421e30c118bb9ad..a437a755c8dbb52ceb80f45a8e99125089b8987e 100644 --- a/aws/sdk/integration-tests/s3/tests/streaming-response.rs +++ b/aws/sdk/integration-tests/s3/tests/streaming-response.rs @@ -4,12 +4,12 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{Client, Credentials, Region}; use aws_smithy_types::error::display::DisplayErrorContext; use bytes::BytesMut; use std::future::Future; use std::net::SocketAddr; +use std::sync::Arc; use std::time::Duration; use tracing::debug; @@ -22,7 +22,7 @@ async fn test_streaming_response_fails_when_eof_comes_before_content_length_reac let _ = tokio::spawn(server); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .endpoint_url(format!("http://{server_addr}")) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/timeouts.rs b/aws/sdk/integration-tests/s3/tests/timeouts.rs index 5d1cacc5aa7833a518cedfd66bc6821416f9d340..83da5c0860e1a292225b1d485ab2e2f9adbce29a 100644 --- a/aws/sdk/integration-tests/s3/tests/timeouts.rs +++ b/aws/sdk/integration-tests/s3/tests/timeouts.rs @@ -4,7 +4,6 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::model::{ CompressionType, CsvInput, CsvOutput, ExpressionType, FileHeaderInfo, InputSerialization, OutputSerialization, @@ -26,7 +25,7 @@ use tokio::time::timeout; async fn test_timeout_service_ends_request_that_never_completes() { let sdk_config = SdkConfig::builder() .region(Region::from_static("us-east-2")) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .http_connector(NeverConnector::new()) .timeout_config( TimeoutConfig::builder() @@ -103,7 +102,7 @@ async fn test_read_timeout() { ) .endpoint_url(format!("http://{server_addr}")) .region(Some(Region::from_static("us-east-1"))) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .build(); let client = Client::new(&config); @@ -146,7 +145,7 @@ async fn test_connect_timeout() { "http://172.255.255.0:18104", ) .region(Some(Region::from_static("us-east-1"))) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .build(); let client = Client::new(&config); diff --git a/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs b/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs index 8d72c5287809b40ca2f681935c994ea2127f36a7..f8947b3d83062ecc235f7af232ff2bbb602edca3 100644 --- a/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs +++ b/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs @@ -4,15 +4,15 @@ */ use aws_config::SdkConfig; -use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{AppName, Client, Credentials, Region}; use aws_smithy_client::test_connection::capture_request; +use std::sync::Arc; #[tokio::test] async fn user_agent_app_name() { let (conn, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .app_name(AppName::new("test-app-name").expect("valid app name")) // set app name in config diff --git a/aws/sdk/integration-tests/s3control/tests/signing-it.rs b/aws/sdk/integration-tests/s3control/tests/signing-it.rs index 4a9857b17a827972831232423b082bb79142a1ec..0aa31bb8f412674c6489f7957b413c34fecb2c96 100644 --- a/aws/sdk/integration-tests/s3control/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3control/tests/signing-it.rs @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_credential_types::provider::SharedCredentialsProvider; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3control::{Client, Credentials, Region}; use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; use aws_types::SdkConfig; use std::convert::Infallible; +use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] @@ -26,7 +26,7 @@ async fn test_signer() { http::Response::builder().status(200).body("").unwrap(), )]); let sdk_config = SdkConfig::builder() - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .credentials_provider(Arc::new(Credentials::for_tests())) .http_connector(conn.clone()) .region(Region::new("us-east-1")) .build(); diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt index 119d43ebfda482793f119c0ff17fd214b4f80ada..84a6d5f38a177f5085619c587588006a9a4aea8a 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt @@ -213,9 +213,16 @@ class ResiliencyConfigCustomization(codegenContext: CodegenContext) : ConfigCust ) ServiceConfig.BuilderBuild -> rustTemplate( + // We call clone on sleep_impl because the field is used by + // initializing the credentials_cache field later in the build + // method of a Config builder. + // We could rearrange the order of decorators so that AwsCodegenDecorator + // runs before RequiredCustomizations, which in turns renders + // CredentialsCacheDecorator before this class, but that is a bigger + // change than adding a call to the clone method on sleep_impl. """ retry_config: self.retry_config, - sleep_impl: self.sleep_impl, + sleep_impl: self.sleep_impl.clone(), timeout_config: self.timeout_config, """, *codegenScope,