From d040f76fd1ae0295b9f4475d994e3dd4f4aa8893 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Tue, 17 Jan 2023 11:18:18 -0600 Subject: [PATCH] Ensure SDK credential cache type safety (#2122) * Add the `aws-credential-types` crate This commit adds a new crate `aws-credential-types` to the `rust-runtime` workspace. This lays the groundwork for being able to create a `LazyCachingCredentialsProvider` outside the `aws-config` crate according to the proposed solution in https://github.com/awslabs/smithy-rs/pull/2082. We have moved the following into this new crate: - Items in aws_types::credentials and and their dependencies - Items in aws_config::meta::credentials and their dependencies Finally, the crate comes with auxiliary files that are present in the other crates in the `rust-runtime` workspace such as `external-types.toml`. * Make `aws-types` depend on `aws-credential-types` The credentials module has been moved from the `aws-types` crate to the `aws-credential-types` crate. This leads to some of the items in the `aws-types` crate adjusting their use statements to point to `aws-credential-types`. The `TimeSource` struct has also been moved to `aws-credential-types` because it is used by `LazyCachingCredentialsProvider`. We have decided to move it instead of duplicating it because `aws-config` was creating a `TimeSource` from `aws-types` and then passing it to the builder for `LazyCachingCredentialsProvider`. If we had duplicated the implementation of `TimeSource` in `aws-credential-types`, two `TimeSource` implementations would have been considered different types and the said use case in `aws-config` would have been broken. * Make `aws-config` depend on `aws-credential-types` The `cache` module and modules in `meta::credentials` (except for `chain`) have been moved to `aws-credential-types`. Again, the goal of restructuring is to allow `LazyCachingCredentialsProvider` to be created outside the `aws-config` crate. While doing so, we try not moving all the default credential provider implementations. * Make `aws-http` depend on `aws-credential-types` This commit adjusts the use statements for the items that have been moved from the `aws-types` crate to the `aws-credential-types` crate. * Make `aws-inlineable` depend on `aws-credential-types` This commit adjusts the use statements for the items that have been moved from the `aws-types` crate to the `aws-credential-types` crate. * Make `aws-sig-auth` depend on `aws-credential-types` This commit adjusts the use statements for the items that have been moved from the `aws-types` crate to the `aws-credential-types` crate. * Emit `aws-credential-types` to the build directory This commit adds `aws-credential-types` to AWS_SDK_RUNTIME so that the build command `/gradlew :aws:sdk:assemble` can generate the crate into sdk/build/aws-sdk. * Make codegen aware of `aws-credential-types` This commit allows the codegen to handle the `aws-credential-types` crate. The items that have been moved from `aws-types` should now be prefixed with `aws-credential-types` when generating fully qualified names. * Make `dynamo-tests` depend on `aws-credential-types` This commit adjusts the use statements for the items that have been moved from the `aws-types` crate to the `aws-credential-types` crate. * Make `s3-tests` depend on `aws-credential-types` This commit adjusts the use statements for the items that have been moved from the `aws-types` crate to the `aws-credential-types` crate. * Make `s3control` depend on `aws-credential-types` This commit adjusts the use statements for the items that have been moved from the `aws-types` crate to the `aws-credential-types` crate. * Update external-types.xml in rust-runtime crates This commit fixes CI failures related to `cargo check-external-types` in ec994be. * Update the file permission on additional-ci * Remove unused dependency from aws-credential-types * Clean up features for aws-credential-types This commit fixes a CI failure where the feature hardcoded-credentials needed other features, aws-smithy-async/rt-tokio and tokio/rt, for the test code to compile with --no-default-features. * Update sdk-external-types.toml * Update aws/rust-runtime/aws-credential-types/Cargo.toml Co-authored-by: John DiSanti * Update aws/rust-runtime/aws-credential-types/README.md Co-authored-by: John DiSanti * Update aws/rust-runtime/aws-credential-types/README.md Co-authored-by: John DiSanti * Update aws/rust-runtime/aws-credential-types/additional-ci Co-authored-by: John DiSanti * Update aws/rust-runtime/aws-credential-types/src/lib.rs Co-authored-by: John DiSanti * Reduce re-exports from `aws-credential-types` This commit reduces the number of re-exports from `aws-credential-types`. The rationale here is that if we add more items to this crate later on, we may get some name collisions in root. Since this crate is not used by our customers directly, it is acceptable for items to take a bit of typing to get to. * Fix broken intra doc link This commit fixes a broken intra doc link that went unnoticed because the offending link was behind the feature `hardcoded-credentials`. * Introduce type and trait for credential caching This commit introduces the following items in the `aws-credential-types` crate: * CredentialsCache * ProvideCachedCredentials `CredentialsCache` is a struct a user will be interacting with when creating a credentials cache; the user no longer creates a concrete credentials cache directly, and instead it is taken care of by `CredentialsCache` behind the scene. `ProvideCachedCredentials` is a trait that will be implemented by concrete credentials caches. Furthermore, this commit renames the following structs according to the RFC https://github.com/awslabs/smithy-rs/pull/1842: * SharedCredentialsProvider -> SharedCredentialsCache * LazyCachingCredentialsProvider -> LazyCredentialsCache * Add `credentials_cache` to `SdkConfig` and to builder This commit adds a new field `credentials_cache` to `SdkConfig`. It also adds a new method `credentials_cache` to `sdk_config::Builder`. They will help a `CredentialsCache` be threaded through from `ConfigLoader` to a service specific client's `config::Builder`, which will be implemented in a subsequent commit. * Put `SharedCredentialsCache` into the property bag This commit updates what goes into the property bag. Now that `SharedCredentialsProvider` has been renamed to `SharedCredentialsCache`, that's what goes into the property bag. Once a `SharedCredentialsCache` is retrieved from the bag, credentials can be obtained by calling `provide_cached_credentials`. * Thread through `credentials_cache` to service client This commit threads through `credentials_cache` to service client's `Config` and its builder. The builder will be the single-sourced place for creating a credentials cache. * Update `aws-config` to use `CredentialsCache` This commit updates `aws-config` to use `CredentialsCache`. Specifically, * `ConfigLoader` now has `credentials_cache` to take `CredentialsCache` * No more `LazyCachingCredentialsProvider` in `DefaultCredentialsChain` * No more `LazyCachingCredentialsProvider` in `AssumeRoleProvider` The second and third bullet points are a result of a credentials cache being composed in a service client's `config::Builder` rather than `DefaultCredentialsChain` or `AssumeRoleProvider` holding it as its field. * Update sdk integration tests This commit bulk updates the integration tests for SDK. Most updates replace the previous `SharedCredentialsProvider::new` with `Arc::new`. A more subtle but important change is to respect the `sleep_impl` field within the build method of a Config builder, making sure to thread it a default `LazyCredentialsCache` created within the build method. If we missed this step, the default constructed `LazyCredentialsCache` would later use the default Tokio sleep impl even during tests that exercise different async runtime, causing them to fail. * Update aws/rust-runtime/aws-credential-types/README.md Co-authored-by: Zelda Hessler * Rename variants of `aws_credential_types::time_source::Inner` This commit addresses https://github.com/awslabs/smithy-rs/pull/2108#discussion_r1053637722 * Split the unit test for `time_source` into two This commit addresses https://github.com/awslabs/smithy-rs/pull/2108#discussion_r1053638381 * Update CHANGELOG.next.toml * Fix test failures in CI coming from `aws-inlineable` This commit fixes test failures in CI coming from the integration test in the `aws-inlineable` crate. Commit ea47572 should have included this change. * Update external-types TOML files * Clean up offending use statements left after merging main * Remove moved module wrongly brought in after merging main * Remove `credentials_cache` from `Builder` for `DefaultCredentialsChain` This commit removes a field `credentials_cache` from the `Builder` for `DefaultCredentialsChain` as it no longer stores `LazyCredentialsCache`. Furthermore, we have also removed methods on the builder that referred to the field `credentials_cache`. After this commit, certain use cases will be broken, i.e. when a user sets timeout for loading credentials via `load_timeout` on the builder, the configured timeout will be dropped on the floor because it will not be threaded through the field `credentials_cache`. We will later provide instructions for how to update those use cases with our new set of APIs. * Remove `configure` from `LazyCredentialsCache` builder This commit removes the `configure` method from the builder for `LazyCredentialsCache`. We tried our best to keep it when we had moved the builder from `aws-config` but had to modify the method signature to destructure `ProviderCondig` to obtain the two fields out of it (`ProviderConfig` lives in `aws-config` so cannot be passed to the builder for `LazyCredentialsCache` which lives in `aws-credential-types`). Given `configure` is technically meant for credentials providers, which `LazyCredentialsCache` is not anymore, we might as well remove it and accomplish the same effect by having customers use both `time_source` and `set_sleep` on the builder instead. * Update CHANGELOG.next.toml * Update CHANGELOG.next.toml * Use unwrap_or_else to simplify two assignments This commit addresses https://github.com/awslabs/smithy-rs/pull/2122#discussion_r1056497419 * Add doc link to `CredentialsCache` to builder method This commit addresses https://github.com/awslabs/smithy-rs/pull/2122#discussion_r1056500448. In addition, it moves rustdoc for `LazyCredentialsCache` to `LazyBuilder` as `LazyCredentialsCache` has been made `pub(crate)` from `pub` and `LazyBuilder` is now a `pub` item instead. * Make `CredentialsCache` configurable in `AssumeRoleProviderBuilder` This commit addresses https://github.com/awslabs/smithy-rs/pull/2122#discussion_r1066400431. It allows users to pass their `CredentialsCache` to the builder just as they do in `SdkConfig`. * Update CHANGELOG.next.toml This commit addresses https://github.com/awslabs/smithy-rs/pull/2122#discussion_r1066400431. Co-authored-by: Yuki Saito Co-authored-by: John DiSanti Co-authored-by: Zelda Hessler --- CHANGELOG.next.toml | 354 ++++++++++++++++++ .../aws-config/external-types.toml | 2 +- .../src/default_provider/credentials.rs | 80 +--- aws/rust-runtime/aws-config/src/lib.rs | 53 ++- .../aws-config/src/profile/credentials.rs | 2 +- .../aws-config/src/profile/profile_file.rs | 1 + .../aws-config/src/sts/assume_role.rs | 91 +++-- .../aws-credential-types/README.md | 2 +- .../aws-credential-types/src/cache.rs | 213 ++++------- .../src/cache/expiring_cache.rs | 162 ++++++++ .../src/{ => cache}/lazy_caching.rs | 189 +++++----- .../aws-credential-types/src/lib.rs | 5 +- .../aws-credential-types/src/provider.rs | 38 -- aws/rust-runtime/aws-http/external-types.toml | 2 +- aws/rust-runtime/aws-http/src/auth.rs | 88 ++--- .../tests/middleware_e2e_test.rs | 9 +- .../aws-types/external-types.toml | 3 +- aws/rust-runtime/aws-types/src/sdk_config.rs | 70 +++- .../smithy/rustsdk/AwsCodegenDecorator.kt | 1 + .../amazon/smithy/rustsdk/CredentialCaches.kt | 146 ++++++++ .../smithy/rustsdk/CredentialProviders.kt | 62 +-- .../dynamodb/tests/cloning.rs | 4 +- .../dynamodb/tests/timeouts.rs | 5 +- .../s3/tests/alternative-async-runtime.rs | 29 +- .../integration-tests/s3/tests/checksums.rs | 6 +- .../integration-tests/s3/tests/concurrency.rs | 5 +- .../s3/tests/customizable-operation.rs | 4 +- .../integration-tests/s3/tests/endpoints.rs | 4 +- .../s3/tests/ignore-invalid-xml-body-root.rs | 4 +- .../s3/tests/naughty-string-metadata.rs | 4 +- .../s3/tests/normalize-uri-path.rs | 4 +- .../query-strings-are-correctly-encoded.rs | 4 +- .../s3/tests/recursion-detection.rs | 4 +- .../s3/tests/select-object-content.rs | 4 +- .../integration-tests/s3/tests/signing-it.rs | 4 +- .../s3/tests/streaming-response.rs | 4 +- .../integration-tests/s3/tests/timeouts.rs | 7 +- .../s3/tests/user-agent-app-name.rs | 4 +- .../s3control/tests/signing-it.rs | 4 +- .../ResiliencyConfigCustomization.kt | 9 +- 40 files changed, 1108 insertions(+), 578 deletions(-) create mode 100644 aws/rust-runtime/aws-credential-types/src/cache/expiring_cache.rs rename aws/rust-runtime/aws-credential-types/src/{ => cache}/lazy_caching.rs (72%) create mode 100644 aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 17aede3d2..7a81809ae 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 e464c19c5..e47352e15 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 075858473..c2a31b5e7 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 cc91fda51..d1b93f899 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 c429c8563..845197f95 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 ac7dee376..a90472c8e 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 8b4da9466..422b64415 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 fa40e5695..e77385e1a 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 5387dd402..6a6c740d0 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 000000000..67556aad9 --- /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 cc2c6e662..c448e862b 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 01af14216..1f790c3fc 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 7f0e6282f..497887597 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 4e648122c..33a2ed14a 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 8389bb9e4..97d943f1d 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 cb7f4e039..04fd7ca61 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 37f3ce94c..b1b4a8629 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 000000000..15e49945e --- /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 001b35b83..b584b14c6 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 4fd3c500f..c48a6d3bf 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 5695b403d..683afe3aa 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 03c058f4d..df67026e9 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 79f00a2a3..78a52c354 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 c54d303d4..29d9c8ae4 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 f0b584aa1..199b5edbb 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 93c37f5c8..d0a9f3501 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 e2f850cb6..09e31930b 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 fd537a539..d00ce6403 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 d039064cb..c4e73812f 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 858d01384..381d55544 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 c4d739447..fb3486444 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 d68af2f68..ec22dfdb1 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 0efdc9a5f..d9b2e56a1 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 8ff6d6eb3..a437a755c 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 5d1cacc5a..83da5c086 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 8d72c5287..f8947b3d8 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 4a9857b17..0aa31bb8f 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 119d43ebf..84a6d5f38 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, -- GitLab