From 74fe9f73f71033dae6fb632eb53b35c257e62c18 Mon Sep 17 00:00:00 2001 From: Noel Date: Tue, 23 Jul 2024 08:14:04 -0700 Subject: [PATCH] update Rust toolchain to 1.79, add basic healthcheck API --- crates/s3/src/error.rs | 21 +++++++++++++++++++-- crates/s3/src/service.rs | 26 ++++++++++++++++++++------ flake.lock | 15 ++++++--------- remi/src/lib.rs | 6 +++++- rust-toolchain.toml | 2 +- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/crates/s3/src/error.rs b/crates/s3/src/error.rs index 05de26e..fa1b3c2 100644 --- a/crates/s3/src/error.rs +++ b/crates/s3/src/error.rs @@ -24,8 +24,8 @@ use aws_sdk_s3::{ operation::{ create_bucket::CreateBucketError, delete_object::DeleteObjectError, get_object::GetObjectError, - head_object::HeadObjectError, list_buckets::ListBucketsError, list_objects_v2::ListObjectsV2Error, - put_object::PutObjectError, + head_bucket::HeadBucketError, head_object::HeadObjectError, list_buckets::ListBucketsError, + list_objects_v2::ListObjectsV2Error, put_object::PutObjectError, }, primitives::SdkBody, }; @@ -120,6 +120,10 @@ pub enum Error { /// Occurs when an error occurred when transforming AWS S3's responses. ByteStream(aws_sdk_s3::primitives::ByteStreamError), + /// Occurs when `remi-s3` cannot perform a HEAD request to the current bucket. This is mainly + /// used in healthchecks to determine if the storage service is ok. + HeadBucket(HeadBucketError), + /// Something that `remi-s3` has emitted on its own. Library(Cow<'static, str>), } @@ -150,6 +154,7 @@ impl Display for Error { E::ListBuckets(err) => Display::fmt(err, f), E::ListObjectsV2(err) => Display::fmt(err, f), E::PutObject(err) => Display::fmt(err, f), + E::HeadBucket(err) => Display::fmt(err, f), E::Library(msg) => f.write_str(msg), } } @@ -253,6 +258,18 @@ impl From>> for Error { } } +impl From>> for Error { + fn from(value: SdkError>) -> Self { + match value { + SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err), + SdkError::DispatchFailure(err) => Self::DispatchFailure(err), + SdkError::TimeoutError(err) => Self::TimeoutError(err), + SdkError::ResponseError(err) => Self::Response(err), + err => Error::HeadBucket(err.into_service_error()), + } + } +} + impl From for Error { fn from(value: aws_sdk_s3::primitives::ByteStreamError) -> Self { Self::ByteStream(value) diff --git a/crates/s3/src/service.rs b/crates/s3/src/service.rs index 8e5aa8e..d88dbb7 100644 --- a/crates/s3/src/service.rs +++ b/crates/s3/src/service.rs @@ -30,7 +30,7 @@ use bytes::Bytes; use remi::{Blob, Directory, File, ListBlobsRequest, UploadRequest}; use std::{borrow::Cow, path::Path}; -const DEFAULT_CONTENT_TYPE: &str = "application/octet; charset=utf-8"; +const DEFAULT_CONTENT_TYPE: &str = "application/octet-stream"; /// Represents an implementation of [`StorageService`] for Amazon Simple Storage Service. #[derive(Debug, Clone)] @@ -99,10 +99,6 @@ impl StorageService { #[async_trait] impl remi::StorageService for StorageService { - // this has to stay `io::Error` since `SdkError` requires too much information - // and this can narrow down. - // - // TODO(@auguwu): this can be a flat error if we could do? type Error = crate::Error; fn name(&self) -> Cow<'static, str> { @@ -483,6 +479,7 @@ impl remi::StorageService for StorageService { Ok(true) } + Err(e) => { let inner = e.into_service_error(); if inner.is_not_found() { @@ -525,7 +522,7 @@ impl remi::StorageService for StorageService { self.client .put_object() - .bucket(self.config.bucket.clone()) + .bucket(&self.config.bucket) .key(normalized) .acl( self.config @@ -545,6 +542,23 @@ impl remi::StorageService for StorageService { .map(|_| ()) .map_err(From::from) } + + #[cfg_attr(feature = "tracing", tracing::instrument(name = "remi.s3.healthcheck", skip_all))] + async fn healthcheck(&self) -> Result<(), Self::Error> { + #[cfg(feature = "log")] + log::trace!("performing healthcheck..."); + + #[cfg(feature = "tracing")] + tracing::trace!("performing healthcheck..."); + + self.client + .head_bucket() + .bucket(&self.config.bucket) + .send() + .await + .map(|_| ()) + .map_err(From::from) + } } #[cfg(test)] diff --git a/flake.lock b/flake.lock index 018d00f..f3fe8f4 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716358718, - "narHash": "sha256-NQbegJb2ZZnAqp2EJhWwTf6DrZXSpA6xZCEq+RGV1r0=", + "lastModified": 1721622093, + "narHash": "sha256-iQ+quy3A1EKeFyLyAtjhgSvZHH7r+xybXZkxMhasN4I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3f316d2a50699a78afe5e77ca486ad553169061e", + "rev": "453402b94f39f968a7c27df28e060f69e4a50c3b", "type": "github" }, "original": { @@ -60,19 +60,16 @@ }, "rust-overlay": { "inputs": { - "flake-utils": [ - "flake-utils" - ], "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1716430594, - "narHash": "sha256-vdVzaGD5p+KG7XHepIeX5rUPmdzEcF2w6rhqfr0SNkI=", + "lastModified": 1721701191, + "narHash": "sha256-nM4szL90VeZHZEC5rFfaiiPNTVOmsihdtk2QSP1l37I=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ee0db3aeebafeaada2b98d076de6d314b4c8682e", + "rev": "4674ff2c2e5423a0cebe16e61aa874c359306af4", "type": "github" }, "original": { diff --git a/remi/src/lib.rs b/remi/src/lib.rs index dad598e..2fb3719 100644 --- a/remi/src/lib.rs +++ b/remi/src/lib.rs @@ -50,7 +50,6 @@ pub trait StorageService: Send + Sync { /// Returns the name of the storage service. /// /// * since 0.1.0 - #[allow(deprecated)] fn name(&self) -> Cow<'static, str> where Self: Sized; @@ -115,6 +114,11 @@ pub trait StorageService: Send + Sync { async fn upload + Send>(&self, path: P, options: UploadRequest) -> Result<(), Self::Error> where Self: Sized; + + /// Performs any healthchecks to determine the storage service's health. + async fn healthcheck(&self) -> Result<(), Self::Error> { + Ok(()) + } } #[cfg(test)] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 570d848..4e92121 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -20,7 +20,7 @@ # SOFTWARE. [toolchain] -channel = "1.78" +channel = "1.79" profile = "minimal" components = [ "rustc",