diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 436b87bb4e..4c7d2e950e 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -81,8 +81,27 @@ references = ["smithy-rs#2539"] meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" } author = "david-perez" +[[smithy-rs]] +message = "Time is now controlled by the `TimeSource` trait. This facilitates testing as well as use cases like WASM where `SystemTime::now()` is not supported." +references = ["smithy-rs#2728", "smithy-rs#2262", "aws-sdk-rust#2087"] +meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" } +author = "rcoh" + +[[smithy-rs]] +message = "The property bag type for Time is now `SharedTimeSource`, not `SystemTime`. If your code relies on setting request time, use `aws_smithy_async::time::SharedTimeSource`." +references = ["smithy-rs#2728", "smithy-rs#2262", "aws-sdk-rust#2087"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "rcoh" + +[[aws-sdk-rust]] +message = "Time is now controlled by the `TimeSource` trait. This facilitates testing as well as use cases like WASM where `SystemTime::now()` is not supported." +references = ["smithy-rs#2728", "smithy-rs#2262", "aws-sdk-rust#2087"] +meta = { "breaking" = false, "tada" = false, "bug" = false } +author = "rcoh" + [[smithy-rs]] message = "Bump dependency on `lambda_http` by `aws-smithy-http-server` to 0.8.0. This version of `aws-smithy-http-server` is only guaranteed to be compatible with 0.8.0, or semver-compatible versions of 0.8.0 of the `lambda_http` crate. It will not work with versions prior to 0.8.0 _at runtime_, making requests to your smithy-rs service unroutable, so please make sure you're running your service in a compatible configuration" author = "david-perez" references = ["smithy-rs#2676", "smithy-rs#2685"] meta = { "breaking" = true, "tada" = false, "bug" = false } + diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index fdcfb1b757..b90a84885c 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -9,6 +9,7 @@ allowed_external_types = [ "aws_credential_types::provider::SharedCredentialsProvider", "aws_sdk_sts::types::_policy_descriptor_type::PolicyDescriptorType", "aws_smithy_async::rt::sleep::AsyncSleep", + "aws_smithy_async::time::TimeSource", "aws_smithy_client::bounds::SmithyConnector", "aws_smithy_client::conns::default_connector::default_connector", "aws_smithy_client::erase::DynConnector", diff --git a/aws/rust-runtime/aws-config/src/imds/client/token.rs b/aws/rust-runtime/aws-config/src/imds/client/token.rs index 4cd3f80075..213243a8cd 100644 --- a/aws/rust-runtime/aws-config/src/imds/client/token.rs +++ b/aws/rust-runtime/aws-config/src/imds/client/token.rs @@ -17,9 +17,9 @@ use crate::imds::client::error::{ImdsError, TokenError, TokenErrorKind}; use crate::imds::client::ImdsResponseRetryClassifier; use aws_credential_types::cache::ExpiringCache; -use aws_credential_types::time_source::TimeSource; use aws_http::user_agent::UserAgentStage; use aws_smithy_async::rt::sleep::AsyncSleep; +use aws_smithy_async::time::SharedTimeSource; use aws_smithy_client::erase::DynConnector; use aws_smithy_client::retry; use aws_smithy_http::body::SdkBody; @@ -65,7 +65,7 @@ pub(super) struct TokenMiddleware { client: Arc>>, token_parser: GetTokenResponseHandler, token: ExpiringCache, - time_source: TimeSource, + time_source: SharedTimeSource, endpoint: Uri, token_ttl: Duration, } @@ -79,7 +79,7 @@ impl Debug for TokenMiddleware { impl TokenMiddleware { pub(super) fn new( connector: DynConnector, - time_source: TimeSource, + time_source: SharedTimeSource, endpoint: Uri, token_ttl: Duration, retry_config: retry::Config, @@ -170,7 +170,7 @@ impl AsyncMapRequest for TokenMiddleware { #[derive(Clone)] struct GetTokenResponseHandler { - time: TimeSource, + time: SharedTimeSource, } impl ParseStrictResponse for GetTokenResponseHandler { diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index 83265c37a5..9cdc47b7c9 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -14,8 +14,8 @@ use crate::imds::client::LazyClient; use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials}; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; -use aws_credential_types::time_source::TimeSource; use aws_credential_types::Credentials; +use aws_smithy_async::time::SharedTimeSource; use aws_types::os_shim_internal::Env; use std::borrow::Cow; use std::error::Error as StdError; @@ -53,7 +53,7 @@ pub struct ImdsCredentialsProvider { client: LazyClient, env: Env, profile: Option, - time_source: TimeSource, + time_source: SharedTimeSource, last_retrieved_credentials: Arc>>, } @@ -390,7 +390,10 @@ mod test { .build(); let creds = provider.provide_credentials().await.expect("valid creds"); // The expiry should be equal to what is originally set (==2021-09-21T04:16:53Z). - assert!(creds.expiry() == UNIX_EPOCH.checked_add(Duration::from_secs(1632197813))); + assert_eq!( + creds.expiry(), + UNIX_EPOCH.checked_add(Duration::from_secs(1632197813)) + ); connection.assert_requests_match(&[]); // There should not be logs indicating credentials are extended for stability. diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 2d237a5b8f..5364d1bcb5 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -155,6 +155,7 @@ mod loader { use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep}; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_client::http_connector::HttpConnector; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; @@ -192,6 +193,7 @@ mod loader { profile_files_override: Option, use_fips: Option, use_dual_stack: Option, + time_source: Option, } impl ConfigLoader { @@ -262,6 +264,12 @@ mod loader { self } + /// Set the time source used for tasks like signing requests + pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { + self.time_source = Some(SharedTimeSource::new(time_source)); + self + } + /// Override the [`HttpConnector`] for this [`ConfigLoader`]. The connector will be used when /// sending operations. This **does not set** the HTTP connector used by config providers. /// To change that connector, use [ConfigLoader::configure]. @@ -563,7 +571,9 @@ mod loader { .unwrap_or_else(|| HttpConnector::ConnectorFn(Arc::new(default_connector))); let credentials_cache = self.credentials_cache.unwrap_or_else(|| { - let mut builder = CredentialsCache::lazy_builder().time_source(conf.time_source()); + let mut builder = CredentialsCache::lazy_builder().time_source( + aws_credential_types::time_source::TimeSource::shared(conf.time_source()), + ); builder.set_sleep(conf.sleep()); builder.into_credentials_cache() }); @@ -588,12 +598,15 @@ mod loader { SharedCredentialsProvider::new(builder.build().await) }; + let ts = self.time_source.unwrap_or_default(); + let mut builder = SdkConfig::builder() .region(region) .retry_config(retry_config) .timeout_config(timeout_config) .credentials_cache(credentials_cache) .credentials_provider(credentials_provider) + .time_source(ts) .http_connector(http_connector); builder.set_app_name(app_name); diff --git a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs index 86622d0c17..838007ad1e 100644 --- a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs +++ b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs @@ -14,6 +14,7 @@ use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentials use aws_credential_types::provider::{self, error::CredentialsError, ProvideCredentials}; use aws_sdk_sts::config::{Builder as StsConfigBuilder, Credentials}; use aws_sdk_sts::Client as StsClient; +use aws_smithy_async::time::SharedTimeSource; use std::fmt::Debug; use std::sync::Arc; @@ -22,6 +23,7 @@ pub(super) struct AssumeRoleProvider { role_arn: String, external_id: Option, session_name: Option, + time_source: SharedTimeSource, } impl AssumeRoleProvider { @@ -35,11 +37,9 @@ impl AssumeRoleProvider { .credentials_provider(input_credentials) .build(); let client = StsClient::from_conf(config); - let session_name = &self - .session_name - .as_ref() - .cloned() - .unwrap_or_else(|| sts::util::default_session_name("assume-role-from-profile")); + let session_name = &self.session_name.as_ref().cloned().unwrap_or_else(|| { + sts::util::default_session_name("assume-role-from-profile", self.time_source.now()) + }); let assume_role_creds = client .assume_role() .role_arn(&self.role_arn) @@ -97,7 +97,12 @@ impl ProviderChain { web_identity_token_file: web_identity_token_file.into(), role_arn: role_arn.to_string(), session_name: session_name.map(|sess| sess.to_string()).unwrap_or_else( - || sts::util::default_session_name("web-identity-token-profile"), + || { + sts::util::default_session_name( + "web-identity-token-profile", + provider_config.time_source().now(), + ) + }, ), }) .configure(provider_config) @@ -140,6 +145,7 @@ impl ProviderChain { role_arn: role_arn.role_arn.into(), external_id: role_arn.external_id.map(|id| id.into()), session_name: role_arn.session_name.map(|id| id.into()), + time_source: provider_config.time_source(), } }) .collect(); diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index 57fa11a621..7d596b2c2e 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -7,6 +7,7 @@ use aws_credential_types::time_source::TimeSource; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep}; +use aws_smithy_async::time::SharedTimeSource; use aws_smithy_client::erase::DynConnector; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; @@ -38,7 +39,7 @@ use crate::profile::{ProfileFileLoadError, ProfileSet}; pub struct ProviderConfig { env: Env, fs: Fs, - time_source: TimeSource, + time_source: SharedTimeSource, connector: HttpConnector, sleep: Option>, region: Option, @@ -72,7 +73,7 @@ impl Default for ProviderConfig { Self { env: Env::default(), fs: Fs::default(), - time_source: TimeSource::default(), + time_source: SharedTimeSource::default(), connector, sleep: default_async_sleep(), region: None, @@ -90,7 +91,6 @@ impl ProviderConfig { /// Unlike [`ProviderConfig::empty`] where `env` and `fs` will use their non-mocked implementations, /// this method will use an empty mock environment and an empty mock file system. pub fn no_configuration() -> Self { - use aws_credential_types::time_source::TestingTimeSource; use std::collections::HashMap; use std::time::UNIX_EPOCH; let fs = Fs::from_raw_map(HashMap::new()); @@ -100,7 +100,7 @@ impl ProviderConfig { profile_files: ProfileFiles::default(), env, fs, - time_source: TimeSource::testing(&TestingTimeSource::new(UNIX_EPOCH)), + time_source: SharedTimeSource::new(UNIX_EPOCH), connector: HttpConnector::Prebuilt(None), sleep: None, region: None, @@ -140,7 +140,7 @@ impl ProviderConfig { ProviderConfig { env: Env::default(), fs: Fs::default(), - time_source: TimeSource::default(), + time_source: SharedTimeSource::default(), connector: HttpConnector::Prebuilt(None), sleep: None, region: None, @@ -179,7 +179,7 @@ impl ProviderConfig { } #[allow(dead_code)] - pub(crate) fn time_source(&self) -> TimeSource { + pub(crate) fn time_source(&self) -> SharedTimeSource { self.time_source.clone() } @@ -293,7 +293,7 @@ impl ProviderConfig { #[doc(hidden)] pub fn with_time_source(self, time_source: TimeSource) -> Self { ProviderConfig { - time_source, + time_source: SharedTimeSource::new(time_source), ..self } } diff --git a/aws/rust-runtime/aws-config/src/sts.rs b/aws/rust-runtime/aws-config/src/sts.rs index 11a6ec591d..edaf1bfc15 100644 --- a/aws/rust-runtime/aws-config/src/sts.rs +++ b/aws/rust-runtime/aws-config/src/sts.rs @@ -22,6 +22,7 @@ impl crate::provider_config::ProviderConfig { .http_connector(expect_connector(self.connector(&Default::default()))) .retry_config(RetryConfig::standard()) .region(self.region()) + .time_source(self.time_source()) .credentials_cache(CredentialsCache::no_caching()); builder.set_sleep_impl(self.sleep()); 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 84343059f9..6ae8f0a6ad 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -211,13 +211,14 @@ impl AssumeRoleProviderBuilder { let mut config = aws_sdk_sts::Config::builder() .credentials_cache(credentials_cache) .credentials_provider(provider) + .time_source(conf.time_source()) .region(self.region.clone()) .http_connector(expect_connector(conf.connector(&Default::default()))); config.set_sleep_impl(conf.sleep()); - let session_name = self - .session_name - .unwrap_or_else(|| super::util::default_session_name("assume-role-provider")); + let session_name = self.session_name.unwrap_or_else(|| { + super::util::default_session_name("assume-role-provider", conf.time_source().now()) + }); let sts_client = StsClient::from_conf(config.build()); let fluent_builder = sts_client diff --git a/aws/rust-runtime/aws-config/src/sts/util.rs b/aws/rust-runtime/aws-config/src/sts/util.rs index 426d3eb40a..c4fd630aef 100644 --- a/aws/rust-runtime/aws-config/src/sts/util.rs +++ b/aws/rust-runtime/aws-config/src/sts/util.rs @@ -7,6 +7,7 @@ use aws_credential_types::provider::{self, error::CredentialsError}; use aws_credential_types::Credentials as AwsCredentials; use aws_sdk_sts::types::Credentials as StsCredentials; +use aws_smithy_async::time::TimeSource; use std::convert::TryFrom; use std::time::{SystemTime, UNIX_EPOCH}; @@ -45,9 +46,7 @@ pub(crate) fn into_credentials( /// STS Assume Role providers MUST assign a name to their generated session. When a user does not /// provide a name for the session, the provider will choose a name composed of a base + a timestamp, /// e.g. `profile-file-provider-123456789` -pub(crate) fn default_session_name(base: &str) -> String { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("post epoch"); +pub(crate) fn default_session_name(base: &str, ts: SystemTime) -> String { + let now = ts.now().duration_since(UNIX_EPOCH).expect("post epoch"); format!("{}-{}", base, now.as_millis()) } diff --git a/aws/rust-runtime/aws-config/src/web_identity_token.rs b/aws/rust-runtime/aws-config/src/web_identity_token.rs index 15da88d6c5..7ea55fdf26 100644 --- a/aws/rust-runtime/aws-config/src/web_identity_token.rs +++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs @@ -65,6 +65,7 @@ use crate::provider_config::ProviderConfig; use crate::sts; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; use aws_sdk_sts::Client as StsClient; +use aws_smithy_async::time::SharedTimeSource; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; use std::borrow::Cow; @@ -80,6 +81,7 @@ const ENV_VAR_SESSION_NAME: &str = "AWS_ROLE_SESSION_NAME"; #[derive(Debug)] pub struct WebIdentityTokenCredentialsProvider { source: Source, + time_source: SharedTimeSource, fs: Fs, sts_client: StsClient, } @@ -131,9 +133,9 @@ impl WebIdentityTokenCredentialsProvider { "AWS_ROLE_ARN environment variable must be set", ) })?; - let session_name = env - .get(ENV_VAR_SESSION_NAME) - .unwrap_or_else(|_| sts::util::default_session_name("web-identity-token")); + let session_name = env.get(ENV_VAR_SESSION_NAME).unwrap_or_else(|_| { + sts::util::default_session_name("web-identity-token", self.time_source.now()) + }); Ok(Cow::Owned(StaticConfiguration { web_identity_token_file: token_file.into(), role_arn, @@ -203,6 +205,7 @@ impl Builder { source, fs: conf.fs(), sts_client: StsClient::from_conf(conf.sts_client_config().build()), + time_source: conf.time_source(), } } } diff --git a/aws/rust-runtime/aws-credential-types/src/time_source.rs b/aws/rust-runtime/aws-credential-types/src/time_source.rs index 2639d2f45a..212c7aa904 100644 --- a/aws/rust-runtime/aws-credential-types/src/time_source.rs +++ b/aws/rust-runtime/aws-credential-types/src/time_source.rs @@ -3,31 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_smithy_async::time::{SharedTimeSource, TimeSource as TimeSourceTrait}; use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; +impl TimeSourceTrait for TimeSource { + fn now(&self) -> SystemTime { + self.now() + } +} + /// Time source abstraction /// /// Simple abstraction representing time either real-time or manually-specified for testing -/// -/// # Examples -/// -/// ```rust -/// # struct Client { -/// # // stub -/// # } -/// # -/// # impl Client { -/// # fn with_timesource(ts: TimeSource) -> Self { -/// # Client { } -/// # } -/// # } -/// use aws_credential_types::time_source::TimeSource; -/// let time = TimeSource::default(); -/// let client = Client::with_timesource(time); -/// ``` #[derive(Debug, Clone)] +// TODO(breakingChangeWindow): Delete this struct pub struct TimeSource(Inner); impl TimeSource { @@ -36,11 +27,17 @@ impl TimeSource { TimeSource(Inner::Testing(time_source.clone())) } + /// Creates `TimeSource` from a shared time source + pub fn shared(time_source: SharedTimeSource) -> Self { + TimeSource(Inner::Shared(time_source)) + } + /// Returns the current system time based on the mode. pub fn now(&self) -> SystemTime { match &self.0 { Inner::Default => SystemTime::now(), Inner::Testing(testing) => testing.now(), + Inner::Shared(ts) => ts.now(), } } } @@ -53,6 +50,8 @@ impl Default for TimeSource { } /// Time Source that can be manually moved for tests +/// > This has been superseded by [`aws_smithy_async::time::TimeSource`] and will be removed in a +/// > future release. /// /// # Examples /// @@ -112,12 +111,11 @@ impl TestingTimeSource { } } -// In the future, if needed we can add a time source trait, however, the testing time source -// should cover most test use cases. #[derive(Debug, Clone)] enum Inner { Default, Testing(TestingTimeSource), + Shared(SharedTimeSource), } #[cfg(test)] diff --git a/aws/rust-runtime/aws-inlineable/Cargo.toml b/aws/rust-runtime/aws-inlineable/Cargo.toml index 8836e8c558..5586b75ae2 100644 --- a/aws/rust-runtime/aws-inlineable/Cargo.toml +++ b/aws/rust-runtime/aws-inlineable/Cargo.toml @@ -21,7 +21,7 @@ aws-sig-auth = { path = "../aws-sig-auth" } aws-smithy-checksums = { path = "../../../rust-runtime/aws-smithy-checksums" } aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client" } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } -aws-smithy-http-tower= { path = "../../../rust-runtime/aws-smithy-http-tower" } +aws-smithy-http-tower = { path = "../../../rust-runtime/aws-smithy-http-tower" } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } aws-types = { path = "../aws-types" } @@ -44,6 +44,7 @@ aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http", features = [ aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime" } tempfile = "3.2.0" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } +aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async", features = ["test-util"] } [package.metadata.docs.rs] all-features = true 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 3aefdf5093..3a5c3258d1 100644 --- a/aws/rust-runtime/aws-inlineable/tests/middleware_e2e_test.rs +++ b/aws/rust-runtime/aws-inlineable/tests/middleware_e2e_test.rs @@ -28,6 +28,7 @@ use aws_http::retry::AwsResponseRetryClassifier; use aws_http::user_agent::AwsUserAgent; use aws_inlineable::middleware::DefaultMiddleware; use aws_sig_auth::signer::OperationSigningConfig; +use aws_smithy_async::time::SharedTimeSource; use aws_types::region::SigningRegion; use aws_types::SigningService; @@ -94,7 +95,9 @@ fn test_operation() -> Operation::Ok(req) }) diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index c5bb21ca17..e241ae92e7 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -19,6 +19,7 @@ aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime" } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } +aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-types = { path = "../aws-types" } http = "0.2.3" percent-encoding = "2.1.0" @@ -28,6 +29,7 @@ uuid = { version = "1", features = ["v4", "fast-rng"] } [dev-dependencies] aws-credential-types = { path = "../aws-credential-types", features = ["test-util"] } aws-smithy-protocol-test = { path = "../../../rust-runtime/aws-smithy-protocol-test" } +aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async", features = ["test-util"] } proptest = "1" serde = { version = "1", features = ["derive"]} serde_json = "1" diff --git a/aws/rust-runtime/aws-runtime/src/auth/sigv4.rs b/aws/rust-runtime/aws-runtime/src/auth/sigv4.rs index 8d72556843..f9453eaf71 100644 --- a/aws/rust-runtime/aws-runtime/src/auth/sigv4.rs +++ b/aws/rust-runtime/aws-runtime/src/auth/sigv4.rs @@ -309,7 +309,7 @@ impl HttpRequestSigner for SigV4HttpRequestSigner { ) -> Result<(), BoxError> { let operation_config = Self::extract_operation_config(auth_scheme_endpoint_config, config_bag)?; - let request_time = config_bag.request_time().unwrap_or_default().system_time(); + let request_time = config_bag.request_time().unwrap_or_default().now(); let credentials = if let Some(creds) = identity.data::() { creds @@ -356,18 +356,17 @@ impl HttpRequestSigner for SigV4HttpRequestSigner { #[cfg(feature = "event-stream")] { use aws_smithy_eventstream::frame::DeferredSignerSender; - use aws_smithy_runtime_api::client::orchestrator::RequestTime; use event_stream::SigV4MessageSigner; if let Some(signer_sender) = config_bag.get::() { - let time_override = config_bag.get::().copied(); + let time_source = config_bag.request_time().unwrap_or_default(); signer_sender .send(Box::new(SigV4MessageSigner::new( _signature, credentials.clone(), Region::new(signing_params.region().to_string()).into(), signing_params.service_name().to_string().into(), - time_override, + time_source, )) as _) .expect("failed to send deferred signer"); } @@ -383,8 +382,8 @@ mod event_stream { use aws_credential_types::Credentials; use aws_sigv4::event_stream::{sign_empty_message, sign_message}; use aws_sigv4::SigningParams; + use aws_smithy_async::time::SharedTimeSource; use aws_smithy_eventstream::frame::{Message, SignMessage, SignMessageError}; - use aws_smithy_runtime_api::client::orchestrator::RequestTime; use aws_types::region::SigningRegion; use aws_types::SigningService; @@ -395,7 +394,7 @@ mod event_stream { credentials: Credentials, signing_region: SigningRegion, signing_service: SigningService, - time: Option, + time: SharedTimeSource, } impl SigV4MessageSigner { @@ -404,7 +403,7 @@ mod event_stream { credentials: Credentials, signing_region: SigningRegion, signing_service: SigningService, - time: Option, + time: SharedTimeSource, ) -> Self { Self { last_signature, @@ -421,7 +420,7 @@ mod event_stream { .secret_key(self.credentials.secret_access_key()) .region(self.signing_region.as_ref()) .service_name(self.signing_service.as_ref()) - .time(self.time.unwrap_or_default().system_time()) + .time(self.time.now()) .settings(()); builder.set_security_token(self.credentials.session_token()); builder.build().unwrap() @@ -470,7 +469,7 @@ mod event_stream { Credentials::for_tests(), SigningRegion::from(region), SigningService::from_static("transcribe"), - Some(RequestTime::new(UNIX_EPOCH + Duration::new(1611160427, 0))), + SharedTimeSource::new(UNIX_EPOCH + Duration::new(1611160427, 0)), )); let mut signatures = Vec::new(); for _ in 0..5 { diff --git a/aws/rust-runtime/aws-sig-auth/Cargo.toml b/aws/rust-runtime/aws-sig-auth/Cargo.toml index cb9695f7db..6ef8e4374f 100644 --- a/aws/rust-runtime/aws-sig-auth/Cargo.toml +++ b/aws/rust-runtime/aws-sig-auth/Cargo.toml @@ -15,6 +15,7 @@ aws-credential-types = { path = "../aws-credential-types" } aws-sigv4 = { path = "../aws-sigv4" } aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } +aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-types = { path = "../aws-types" } http = "0.2.2" tracing = "0.1" @@ -22,8 +23,9 @@ tracing = "0.1" [dev-dependencies] aws-credential-types = { path = "../aws-credential-types", features = ["test-util"] } aws-endpoint = { path = "../aws-endpoint" } -aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types"} +aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } tracing-test = "0.2.1" +aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async", features = ["test-util"] } [package.metadata.docs.rs] all-features = true diff --git a/aws/rust-runtime/aws-sig-auth/src/middleware.rs b/aws/rust-runtime/aws-sig-auth/src/middleware.rs index c8c4794f0b..8dc84da5c2 100644 --- a/aws/rust-runtime/aws-sig-auth/src/middleware.rs +++ b/aws/rust-runtime/aws-sig-auth/src/middleware.rs @@ -5,7 +5,6 @@ use std::error::Error; use std::fmt::{Display, Formatter}; -use std::time::SystemTime; use aws_smithy_http::middleware::MapRequest; use aws_smithy_http::operation::Request; @@ -13,6 +12,7 @@ use aws_smithy_http::property_bag::PropertyBag; use aws_credential_types::Credentials; use aws_sigv4::http_request::SignableBody; +use aws_smithy_async::time::SharedTimeSource; use aws_types::region::SigningRegion; use aws_types::SigningService; @@ -54,11 +54,8 @@ impl AsRef for Signature { /// - [`Credentials`](Credentials): Credentials to sign with /// - [`OperationSigningConfig`](OperationSigningConfig): Operation specific signing configuration, e.g. /// changes to URL encoding behavior, or headers that must be omitted. +/// - [`SharedTimeSource`]: The time source to use when signing the request. /// If any of these fields are missing, the middleware will return an error. -/// -/// The following fields MAY be present in the property bag: -/// - [`SystemTime`](SystemTime): The timestamp to use when signing the request. If this field is not present -/// [`SystemTime::now`](SystemTime::now) will be used. #[derive(Clone, Debug)] pub struct SigV4SigningStage { signer: SigV4Signer, @@ -152,9 +149,10 @@ fn signing_config( let payload_override = config.get::>(); let request_config = RequestConfig { request_ts: config - .get::() - .copied() - .unwrap_or_else(SystemTime::now), + .get::() + .map(|t| t.now()) + // TODO(enableNewSmithyRuntime): Remove this fallback + .unwrap_or_else(|| SharedTimeSource::default().now()), region, payload_override, service: signing_service, @@ -192,7 +190,7 @@ impl MapRequest for SigV4SigningStage { // If this is an event stream operation, set up the event stream signer #[cfg(feature = "sign-eventstream")] if let Some(signer_sender) = config.get::() { - let time_override = config.get::().copied(); + let time_override = config.get::().map(|ts| ts.now()); signer_sender .send(Box::new(EventStreamSigV4Signer::new( signature.as_ref().into(), @@ -222,6 +220,7 @@ mod test { use aws_credential_types::Credentials; use aws_endpoint::AwsAuthStage; + use aws_smithy_async::time::SharedTimeSource; use aws_types::region::{Region, SigningRegion}; use aws_types::SigningService; @@ -262,7 +261,6 @@ mod test { fn sends_event_stream_signer_for_event_stream_operations() { use crate::event_stream::SigV4MessageSigner as EventStreamSigV4Signer; use aws_smithy_eventstream::frame::{DeferredSigner, SignMessage}; - use std::time::SystemTime; let (mut deferred_signer, deferred_signer_sender) = DeferredSigner::new(); let req = http::Request::builder() @@ -273,7 +271,9 @@ mod test { let req = operation::Request::new(req) .augment(|req, properties| { properties.insert(region.clone()); - properties.insert::(UNIX_EPOCH + Duration::new(1611160427, 0)); + properties.insert::(SharedTimeSource::new( + UNIX_EPOCH + Duration::new(1611160427, 0), + )); properties.insert(SigningService::from_static("kinesis")); properties.insert(OperationSigningConfig::default_config()); properties.insert(Credentials::for_tests()); @@ -315,7 +315,9 @@ mod test { let req = operation::Request::new(req) .augment(|req, conf| { conf.insert(region.clone()); - conf.insert(UNIX_EPOCH + Duration::new(1611160427, 0)); + conf.insert(SharedTimeSource::new( + UNIX_EPOCH + Duration::new(1611160427, 0), + )); conf.insert(SigningService::from_static("kinesis")); conf.insert(endpoint); Result::<_, Infallible>::Ok(req) diff --git a/aws/rust-runtime/aws-types/external-types.toml b/aws/rust-runtime/aws-types/external-types.toml index 71ce4acd8a..6c1d2b5aab 100644 --- a/aws/rust-runtime/aws-types/external-types.toml +++ b/aws/rust-runtime/aws-types/external-types.toml @@ -2,6 +2,8 @@ allowed_external_types = [ "aws_credential_types::cache::CredentialsCache", "aws_credential_types::provider::SharedCredentialsProvider", "aws_smithy_async::rt::sleep::AsyncSleep", + "aws_smithy_async::time::TimeSource", + "aws_smithy_async::time::SharedTimeSource", "aws_smithy_client::http_connector", "aws_smithy_client::http_connector::HttpConnector", "aws_smithy_http::endpoint::Endpoint", diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index f3a0301ae8..af60e88f5d 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -14,6 +14,7 @@ use std::sync::Arc; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::SharedCredentialsProvider; use aws_smithy_async::rt::sleep::AsyncSleep; +use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_client::http_connector::HttpConnector; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; @@ -40,6 +41,8 @@ If no dual-stack endpoint is available the request MAY return an error. **Note**: Some services do not offer dual-stack as a configurable parameter (e.g. Code Catalyst). For these services, this setting has no effect" }; + + (time_source) => { "The time source use to use for this client. This only needs to be required for creating deterministic tests or platforms where `SystemTime::now()` is not supported." }; } } @@ -53,6 +56,7 @@ pub struct SdkConfig { endpoint_url: Option, retry_config: Option, sleep_impl: Option>, + time_source: Option, timeout_config: Option, http_connector: Option, use_fips: Option, @@ -73,6 +77,7 @@ pub struct Builder { endpoint_url: Option, retry_config: Option, sleep_impl: Option>, + time_source: Option, timeout_config: Option, http_connector: Option, use_fips: Option, @@ -499,6 +504,18 @@ impl Builder { self } + #[doc = docs_for!(time_source)] + pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { + self.set_time_source(Some(SharedTimeSource::new(time_source))); + self + } + + #[doc = docs_for!(time_source)] + pub fn set_time_source(&mut self, time_source: Option) -> &mut Self { + self.time_source = time_source; + self + } + /// Build a [`SdkConfig`](SdkConfig) from this builder pub fn build(self) -> SdkConfig { SdkConfig { @@ -513,6 +530,7 @@ impl Builder { http_connector: self.http_connector, use_fips: self.use_fips, use_dual_stack: self.use_dual_stack, + time_source: self.time_source, } } } @@ -554,6 +572,11 @@ impl SdkConfig { self.credentials_provider.as_ref() } + /// Configured time source + pub fn time_source(&self) -> Option<&SharedTimeSource> { + self.time_source.as_ref() + } + /// Configured app name pub fn app_name(&self) -> Option<&AppName> { self.app_name.as_ref() diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt index ed9a6271a9..754ac52310 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt @@ -23,11 +23,13 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : "BeforeTransmitInterceptorContextMut" to RuntimeType.smithyRuntimeApi(runtimeConfig) .resolve("client::interceptors::BeforeTransmitInterceptorContextMut"), "ConfigBag" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("config_bag::ConfigBag"), - "ConfigBagAccessors" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::ConfigBagAccessors"), + "ConfigBagAccessors" to RuntimeType.smithyRuntimeApi(runtimeConfig) + .resolve("client::orchestrator::ConfigBagAccessors"), "http" to CargoDependency.Http.toType(), "InterceptorContext" to RuntimeType.smithyRuntimeApi(runtimeConfig) .resolve("client::interceptors::InterceptorContext"), - "RequestTime" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::RequestTime"), + "SharedTimeSource" to CargoDependency.smithyAsync(runtimeConfig).withFeature("test-util").toType() + .resolve("time::SharedTimeSource"), "SharedInterceptor" to RuntimeType.smithyRuntimeApi(runtimeConfig) .resolve("client::interceptors::SharedInterceptor"), "TestParamsSetterInterceptor" to CargoDependency.smithyRuntime(runtimeConfig).withFeature("test-util") @@ -45,7 +47,7 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : pub fn request_time_for_tests(mut self, request_time: ::std::time::SystemTime) -> Self { use #{ConfigBagAccessors}; let interceptor = #{TestParamsSetterInterceptor}::new(move |_: &mut #{BeforeTransmitInterceptorContextMut}<'_>, cfg: &mut #{ConfigBag}| { - cfg.set_request_time(#{RequestTime}::new(request_time)); + cfg.set_request_time(#{SharedTimeSource}::new(request_time)); }); self.interceptors.push(#{SharedInterceptor}::new(interceptor)); self @@ -89,7 +91,7 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : ##[doc(hidden)] // This is a temporary method for testing. NEVER use it in production pub fn request_time_for_tests(mut self, request_time: ::std::time::SystemTime) -> Self { - self.operation.properties_mut().insert(request_time); + self.operation.properties_mut().insert(#{SharedTimeSource}::new(request_time)); self } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt index 4c9f6feffc..fb53f61165 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt @@ -25,7 +25,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.protocols.ClientHttpBou import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.docs -import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate @@ -208,12 +207,14 @@ class AwsInputPresignedMethod( *codegenScope, ) rustBlock("") { - rust( + rustTemplate( """ // Change signature type to query params and wire up presigning config let mut props = request.properties_mut(); - props.insert(presigning_config.start_time()); + props.insert(#{SharedTimeSource}::new(presigning_config.start_time())); """, + "SharedTimeSource" to RuntimeType.smithyAsync(runtimeConfig) + .resolve("time::SharedTimeSource"), ) withBlock("props.insert(", ");") { rustTemplate( diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt index f5d8afcbe2..b77b5c9bc7 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt @@ -77,6 +77,7 @@ class GenericSmithySdkConfigSettings : ClientCodegenDecorator { ${section.serviceConfigBuilder}.set_sleep_impl(${section.sdkConfig}.sleep_impl()); ${section.serviceConfigBuilder}.set_http_connector(${section.sdkConfig}.http_connector().cloned()); + ${section.serviceConfigBuilder}.set_time_source(${section.sdkConfig}.time_source().cloned()); """, ) }, diff --git a/aws/sdk/integration-tests/kms/Cargo.toml b/aws/sdk/integration-tests/kms/Cargo.toml index fbed1109f7..188a2d42dd 100644 --- a/aws/sdk/integration-tests/kms/Cargo.toml +++ b/aws/sdk/integration-tests/kms/Cargo.toml @@ -16,10 +16,11 @@ aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime" } aws-sdk-kms = { path = "../../build/aws-sdk/sdk/kms" } aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api" } bytes = "1.0.0" http = "0.2.0" -tokio = { version = "1.23.1", features = ["full", "test-util"]} +tokio = { version = "1.23.1", features = ["full", "test-util"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } diff --git a/aws/sdk/integration-tests/s3/tests/checksums.rs b/aws/sdk/integration-tests/s3/tests/checksums.rs index ae533964a1..be9c860628 100644 --- a/aws/sdk/integration-tests/s3/tests/checksums.rs +++ b/aws/sdk/integration-tests/s3/tests/checksums.rs @@ -59,6 +59,7 @@ async fn test_checksum_on_streaming_response( ); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .time_source(UNIX_EPOCH + Duration::from_secs(1624036048)) .region(Region::new("us-east-1")) .http_connector(conn.clone()) .build(); @@ -73,7 +74,6 @@ async fn test_checksum_on_streaming_response( .customize() .await .unwrap() - .request_time_for_tests(UNIX_EPOCH + Duration::from_secs(1624036048)) .user_agent_for_tests() .send() .await diff --git a/aws/sra-test/integration-tests/aws-sdk-s3/tests/interceptors.rs b/aws/sra-test/integration-tests/aws-sdk-s3/tests/interceptors.rs index b1e708aad1..b69d67b3f3 100644 --- a/aws/sra-test/integration-tests/aws-sdk-s3/tests/interceptors.rs +++ b/aws/sra-test/integration-tests/aws-sdk-s3/tests/interceptors.rs @@ -8,6 +8,7 @@ mod util; use aws_http::user_agent::AwsUserAgent; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; +use aws_smithy_async::test_util::StaticTimeSource; use aws_smithy_client::dvr; use aws_smithy_client::dvr::MediaType; use aws_smithy_client::erase::DynConnector; @@ -15,7 +16,6 @@ use aws_smithy_runtime_api::client::interceptors::{ BeforeTransmitInterceptorContextMut, Interceptor, }; use aws_smithy_runtime_api::client::orchestrator::ConfigBagAccessors; -use aws_smithy_runtime_api::client::orchestrator::RequestTime; use aws_smithy_runtime_api::config_bag::ConfigBag; use http::header::USER_AGENT; use http::HeaderValue; @@ -66,7 +66,7 @@ impl Interceptor for RequestTimeResetInterceptor { _context: &mut BeforeTransmitInterceptorContextMut<'_>, cfg: &mut ConfigBag, ) -> Result<(), aws_smithy_runtime_api::client::interceptors::BoxError> { - cfg.set_request_time(RequestTime::new(UNIX_EPOCH)); + cfg.set_request_time(StaticTimeSource::new(UNIX_EPOCH)); Ok(()) } @@ -81,7 +81,7 @@ impl Interceptor for RequestTimeAdvanceInterceptor { cfg: &mut ConfigBag, ) -> Result<(), aws_smithy_runtime_api::client::interceptors::BoxError> { let request_time = cfg.request_time().unwrap(); - let request_time = RequestTime::new(request_time.system_time() + self.0); + let request_time = StaticTimeSource::new(request_time.now() + self.0); cfg.set_request_time(request_time); Ok(()) diff --git a/aws/sra-test/integration-tests/aws-sdk-s3/tests/request_information_headers.rs b/aws/sra-test/integration-tests/aws-sdk-s3/tests/request_information_headers.rs index a5138f40de..afaf9d2718 100644 --- a/aws/sra-test/integration-tests/aws-sdk-s3/tests/request_information_headers.rs +++ b/aws/sra-test/integration-tests/aws-sdk-s3/tests/request_information_headers.rs @@ -13,15 +13,14 @@ use aws_smithy_client::dvr::MediaType; use aws_smithy_client::erase::DynConnector; use aws_smithy_runtime::client::retries::strategy::FixedDelayRetryStrategy; use aws_smithy_runtime_api::client::interceptors::InterceptorRegistrar; -use aws_smithy_runtime_api::client::orchestrator::{ConfigBagAccessors, RequestTime}; +use aws_smithy_runtime_api::client::orchestrator::ConfigBagAccessors; use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin; use aws_smithy_runtime_api::config_bag::ConfigBag; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, UNIX_EPOCH}; #[derive(Debug)] struct FixupPlugin { client: Client, - timestamp: SystemTime, } // # One SDK operation invocation. @@ -44,7 +43,6 @@ async fn three_retries_and_then_success() { .bucket("test-bucket"); cfg.put(params_builder); - cfg.set_request_time(RequestTime::new(self.timestamp.clone())); cfg.put(AwsUserAgent::for_tests()); cfg.put(InvocationId::for_tests()); cfg.set_retry_strategy(FixedDelayRetryStrategy::one_second_delay()); @@ -58,11 +56,11 @@ async fn three_retries_and_then_success() { .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .http_connector(DynConnector::new(conn.clone())) + .time_source(UNIX_EPOCH + Duration::from_secs(1624036048)) .build(); let client = Client::from_conf(config); let fixup = FixupPlugin { client: client.clone(), - timestamp: UNIX_EPOCH + Duration::from_secs(1624036048), }; let resp = dbg!( diff --git a/aws/sra-test/integration-tests/aws-sdk-s3/tests/util.rs b/aws/sra-test/integration-tests/aws-sdk-s3/tests/util.rs index 7ce99e3af3..4c9864ae84 100644 --- a/aws/sra-test/integration-tests/aws-sdk-s3/tests/util.rs +++ b/aws/sra-test/integration-tests/aws-sdk-s3/tests/util.rs @@ -5,10 +5,11 @@ use aws_http::user_agent::AwsUserAgent; use aws_runtime::invocation_id::InvocationId; +use aws_smithy_async::test_util::StaticTimeSource; use aws_smithy_runtime_api::client::interceptors::{ BeforeTransmitInterceptorContextMut, Interceptor, InterceptorRegistrar, }; -use aws_smithy_runtime_api::client::orchestrator::{ConfigBagAccessors, RequestTime}; +use aws_smithy_runtime_api::client::orchestrator::ConfigBagAccessors; use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin; use aws_smithy_runtime_api::config_bag::ConfigBag; use http::header::USER_AGENT; @@ -18,16 +19,13 @@ use std::time::SystemTime; pub const X_AMZ_USER_AGENT: HeaderName = HeaderName::from_static("x-amz-user-agent"); #[derive(Debug)] -pub struct FixupPlugin { - pub timestamp: SystemTime, -} +pub struct FixupPlugin; impl RuntimePlugin for FixupPlugin { fn configure( &self, cfg: &mut ConfigBag, _interceptors: &mut InterceptorRegistrar, ) -> Result<(), aws_smithy_runtime_api::client::runtime_plugin::BoxError> { - cfg.set_request_time(RequestTime::new(self.timestamp.clone())); cfg.put(InvocationId::for_tests()); Ok(()) } 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 5fcab7bd33..13cc1c26ba 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 @@ -276,6 +276,7 @@ class ResiliencyServiceRuntimePluginCustomization : ServiceRuntimePluginCustomiz if let Some(timeout_config) = self.handle.conf.timeout_config() { ${section.configBagName}.put(timeout_config.clone()); } + ${section.configBagName}.put(self.handle.conf.time_source.clone()); """, ) } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt index d43c5706ec..c6116f7326 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt @@ -18,6 +18,8 @@ import software.amazon.smithy.rust.codegen.client.smithy.customizations.Resilien import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyServiceRuntimePluginCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.config.TimeSourceOperationCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.config.timeSourceCustomization import software.amazon.smithy.rust.codegen.core.rustlang.Feature import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.customizations.AllowLintsCustomization @@ -47,7 +49,8 @@ class RequiredCustomizations : ClientCodegenDecorator { IdempotencyTokenGenerator(codegenContext, operation) + EndpointPrefixGenerator(codegenContext, operation) + HttpChecksumRequiredGenerator(codegenContext, operation) + - HttpVersionListCustomization(codegenContext, operation) + HttpVersionListCustomization(codegenContext, operation) + + TimeSourceOperationCustomization() override fun configCustomizations( codegenContext: ClientCodegenContext, @@ -57,9 +60,9 @@ class RequiredCustomizations : ClientCodegenDecorator { if (codegenContext.smithyRuntimeMode.generateOrchestrator) { baseCustomizations + ResiliencyConfigCustomization(codegenContext) + InterceptorConfigCustomization( codegenContext, - ) + ) + timeSourceCustomization(codegenContext) } else { - baseCustomizations + ResiliencyConfigCustomization(codegenContext) + baseCustomizations + ResiliencyConfigCustomization(codegenContext) + timeSourceCustomization(codegenContext) } override fun libRsCustomizations( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt index 3112798fe0..4af06de0bd 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt @@ -29,6 +29,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomizat import software.amazon.smithy.rust.codegen.core.smithy.customize.Section import software.amazon.smithy.rust.codegen.core.smithy.makeOptional import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.core.util.letIf /** * [ServiceConfig] is the parent type of sections that can be overridden when generating a config for a service. @@ -103,7 +104,13 @@ sealed class ServiceConfig(name: String) : Section(name) { data class DefaultForTests(val configBuilderRef: String) : ServiceConfig("DefaultForTests") } -data class ConfigParam(val name: String, val type: Symbol, val setterDocs: Writable?, val getterDocs: Writable? = null) +data class ConfigParam( + val name: String, + val type: Symbol, + val setterDocs: Writable?, + val getterDocs: Writable? = null, + val optional: Boolean = true, +) /** * Config customization for a config param with no special behavior: @@ -116,7 +123,11 @@ fun standardConfigParam(param: ConfigParam): ConfigCustomization = object : Conf return when (section) { is ServiceConfig.ConfigStruct -> writable { docsOrFallback(param.getterDocs) - rust("pub (crate) ${param.name}: #T,", param.type.makeOptional()) + val t = when (param.optional) { + true -> param.type.makeOptional() + false -> param.type + } + rust("pub (crate) ${param.name}: #T,", t) } ServiceConfig.ConfigImpl -> emptySection @@ -148,7 +159,8 @@ fun standardConfigParam(param: ConfigParam): ConfigCustomization = object : Conf } ServiceConfig.BuilderBuild -> writable { - rust("${param.name}: self.${param.name},") + val default = "".letIf(!param.optional) { ".unwrap_or_default() " } + rust("${param.name}: self.${param.name}$default,") } ServiceConfig.ToRuntimePlugin -> emptySection @@ -204,6 +216,7 @@ class ServiceConfigGenerator(private val customizations: List writable { + rust( + """ + ${section.request}.properties_mut().insert(${section.config}.time_source.clone()); + """, + ) + } + + else -> emptySection + } + } +} diff --git a/rust-runtime/aws-smithy-async/src/test_util.rs b/rust-runtime/aws-smithy-async/src/test_util.rs index 5a8e864ada..7d3b4f78ce 100644 --- a/rust-runtime/aws-smithy-async/src/test_util.rs +++ b/rust-runtime/aws-smithy-async/src/test_util.rs @@ -13,7 +13,7 @@ use tokio::sync::Barrier; use tokio::time::timeout; use crate::rt::sleep::{AsyncSleep, Sleep}; -use crate::time::TimeSource; +use crate::time::{SharedTimeSource, TimeSource}; /// Manually controlled time source #[derive(Debug, Clone)] @@ -24,7 +24,7 @@ pub struct ManualTimeSource { impl TimeSource for ManualTimeSource { fn now(&self) -> SystemTime { - self.start_time + dbg!(self.log.lock().unwrap()).iter().sum() + self.start_time + self.log.lock().unwrap().iter().sum() } } @@ -194,6 +194,49 @@ pub fn controlled_time_and_sleep( (ManualTimeSource { start_time, log }, sleep, gate) } +#[derive(Debug)] +/// Time source that always returns the same time +pub struct StaticTimeSource { + time: SystemTime, +} + +impl StaticTimeSource { + /// Creates a new static time source that always returns the same time + pub fn new(time: SystemTime) -> Self { + Self { time } + } +} + +impl TimeSource for StaticTimeSource { + fn now(&self) -> SystemTime { + self.time + } +} + +impl TimeSource for SystemTime { + fn now(&self) -> SystemTime { + *self + } +} + +impl From for SharedTimeSource { + fn from(value: StaticTimeSource) -> Self { + SharedTimeSource::new(value) + } +} + +impl From for SharedTimeSource { + fn from(value: SystemTime) -> Self { + SharedTimeSource::new(value) + } +} + +impl From for SharedTimeSource { + fn from(value: ManualTimeSource) -> Self { + SharedTimeSource::new(value) + } +} + #[cfg(test)] mod test { use crate::rt::sleep::AsyncSleep; diff --git a/rust-runtime/aws-smithy-async/src/time.rs b/rust-runtime/aws-smithy-async/src/time.rs index 85c2ff0519..d2e52c9a87 100644 --- a/rust-runtime/aws-smithy-async/src/time.rs +++ b/rust-runtime/aws-smithy-async/src/time.rs @@ -5,6 +5,7 @@ //! Time source abstraction to support WASM and testing use std::fmt::Debug; +use std::sync::Arc; use std::time::SystemTime; /// Trait with a `now()` function returning the current time @@ -30,3 +31,33 @@ impl TimeSource for SystemTimeSource { SystemTime::now() } } + +impl Default for SharedTimeSource { + fn default() -> Self { + SharedTimeSource(Arc::new(SystemTimeSource)) + } +} + +#[derive(Debug, Clone)] +/// Time source structure used inside SDK +/// +/// This implements Default—the default implementation will use `SystemTime::now()` +pub struct SharedTimeSource(Arc); + +impl SharedTimeSource { + /// Returns the current time + pub fn now(&self) -> SystemTime { + self.0.now() + } + + /// Creates a new shared time source + pub fn new(source: impl TimeSource + 'static) -> Self { + Self(Arc::new(source)) + } +} + +impl TimeSource for SharedTimeSource { + fn now(&self) -> SystemTime { + self.0.now() + } +} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs index 3999c365b2..97ff322145 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs @@ -15,6 +15,7 @@ use crate::config_bag::ConfigBag; use crate::type_erasure::{TypeErasedBox, TypedBox}; use aws_smithy_async::future::now_or_later::NowOrLater; use aws_smithy_async::rt::sleep::AsyncSleep; +use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_http::body::SdkBody; use aws_smithy_types::endpoint::Endpoint; use bytes::Bytes; @@ -22,7 +23,6 @@ use std::fmt; use std::future::Future as StdFuture; use std::pin::Pin; use std::sync::Arc; -use std::time::SystemTime; pub use error::OrchestratorError; @@ -78,29 +78,6 @@ pub trait EndpointResolver: Send + Sync + fmt::Debug { fn resolve_endpoint(&self, params: &EndpointResolverParams) -> Result; } -/// Time that the request is being made (so that time can be overridden in the [`ConfigBag`]). -#[non_exhaustive] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct RequestTime(SystemTime); - -impl Default for RequestTime { - fn default() -> Self { - Self(SystemTime::now()) - } -} - -impl RequestTime { - /// Create a new [`RequestTime`]. - pub fn new(time: SystemTime) -> Self { - Self(time) - } - - /// Returns the request time as a [`SystemTime`]. - pub fn system_time(&self) -> SystemTime { - self.0 - } -} - /// Informs the orchestrator on whether or not the request body needs to be loaded into memory before transmit. /// /// This enum gets placed into the `ConfigBag` to change the orchestrator behavior. @@ -161,8 +138,8 @@ pub trait ConfigBagAccessors { fn retry_strategy(&self) -> &dyn RetryStrategy; fn set_retry_strategy(&mut self, retry_strategy: impl RetryStrategy + 'static); - fn request_time(&self) -> Option; - fn set_request_time(&mut self, request_time: RequestTime); + fn request_time(&self) -> Option; + fn set_request_time(&mut self, time_source: impl TimeSource + 'static); fn sleep_impl(&self) -> Option>; fn set_sleep_impl(&mut self, async_sleep: Option>); @@ -288,12 +265,12 @@ impl ConfigBagAccessors for ConfigBag { self.put::>(Box::new(retry_strategy)); } - fn request_time(&self) -> Option { - self.get::().cloned() + fn request_time(&self) -> Option { + self.get::().cloned() } - fn set_request_time(&mut self, request_time: RequestTime) { - self.put::(request_time); + fn set_request_time(&mut self, request_time: impl TimeSource + 'static) { + self.put::(SharedTimeSource::new(request_time)); } fn sleep_impl(&self) -> Option> { diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index 496d50d84b..aadbfcac83 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -30,7 +30,7 @@ tokio = { version = "1.25", features = [] } tracing = "0.1.37" [dev-dependencies] -aws-smithy-async = { path = "../aws-smithy-async", features = ["rt-tokio"] } +aws-smithy-async = { path = "../aws-smithy-async", features = ["rt-tokio", "test-util"] } tokio = { version = "1.25", features = ["macros", "rt", "test-util"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } tracing-test = "0.2.1" diff --git a/rust-runtime/aws-smithy-runtime/src/client/test_util/interceptor.rs b/rust-runtime/aws-smithy-runtime/src/client/test_util/interceptor.rs index 74fa60bd70..543777ba6a 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/test_util/interceptor.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/test_util/interceptor.rs @@ -47,7 +47,7 @@ mod tests { use super::*; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime_api::client::interceptors::InterceptorContext; - use aws_smithy_runtime_api::client::orchestrator::{ConfigBagAccessors, RequestTime}; + use aws_smithy_runtime_api::client::orchestrator::ConfigBagAccessors; use aws_smithy_runtime_api::type_erasure::TypedBox; use std::time::{Duration, UNIX_EPOCH}; @@ -64,15 +64,12 @@ mod tests { let interceptor = TestParamsSetterInterceptor::new({ let request_time = request_time.clone(); move |_: &mut BeforeTransmitInterceptorContextMut<'_>, cfg: &mut ConfigBag| { - cfg.set_request_time(RequestTime::new(request_time)); + cfg.set_request_time(request_time); } }); interceptor .modify_before_signing(&mut ctx, &mut cfg) .unwrap(); - assert_eq!( - request_time, - cfg.get::().unwrap().system_time() - ); + assert_eq!(cfg.request_time().unwrap().now(), request_time); } }