mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-13 16:46:55 +00:00
feat(storage): refactor audit and notification with OperationHelper (#825)
* improve code for audit * improve code ecfs.rs * improve code * improve code for ecfs.rs * feat(storage): refactor audit and notification with OperationHelper This commit introduces a significant refactoring of the audit logging and event notification mechanisms within `ecfs.rs`. The core of this change is the new `OperationHelper` struct, which encapsulates and simplifies the logic for both concerns. It replaces the previous `AuditHelper` and manual event dispatching. Key improvements include: - **Unified Handling**: `OperationHelper` manages both audit and notification builders, providing a single, consistent entry point for S3 operations. - **RAII for Automation**: By leveraging the `Drop` trait, the helper automatically dispatches logs and notifications when it goes out of scope. This simplifies S3 method implementations and ensures cleanup even on early returns. - **Fluent API**: A builder-like pattern with methods such as `.object()`, `.version_id()`, and `.suppress_event()` makes the code more readable and expressive. - **Context-Aware Logic**: The helper's `.complete()` method intelligently populates log details based on the operation's `S3Result` and only triggers notifications on success. - **Modular Design**: All helper logic is now isolated in `rustfs/src/storage/helper.rs`, improving separation of concerns and making `ecfs.rs` cleaner. This refactoring significantly enhances code clarity, reduces boilerplate, and improves the robustness of logging and notification handling across the storage layer. * fix * fix * fix * fix * fix * fix * fix * improve code for audit and notify * fix * fix * fix
This commit is contained in:
@@ -112,7 +112,6 @@ mime_guess = { workspace = true }
|
||||
pin-project-lite.workspace = true
|
||||
rust-embed = { workspace = true, features = ["interpolate-folder-path"] }
|
||||
s3s.workspace = true
|
||||
scopeguard.workspace = true
|
||||
shadow-rs = { workspace = true, features = ["build", "metadata"] }
|
||||
sysinfo = { workspace = true, features = ["multithread"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -134,7 +134,7 @@ impl Operation for NotificationTarget {
|
||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
|
||||
|
||||
// 3. Get notification system instance
|
||||
let Some(ns) = rustfs_notify::global::notification_system() else {
|
||||
let Some(ns) = rustfs_notify::notification_system() else {
|
||||
return Err(s3_error!(InternalError, "notification system not initialized"));
|
||||
};
|
||||
|
||||
@@ -300,7 +300,7 @@ impl Operation for ListNotificationTargets {
|
||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
|
||||
|
||||
// 2. Get notification system instance
|
||||
let Some(ns) = rustfs_notify::global::notification_system() else {
|
||||
let Some(ns) = rustfs_notify::notification_system() else {
|
||||
return Err(s3_error!(InternalError, "notification system not initialized"));
|
||||
};
|
||||
|
||||
@@ -351,7 +351,7 @@ impl Operation for ListTargetsArns {
|
||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
|
||||
|
||||
// 2. Get notification system instance
|
||||
let Some(ns) = rustfs_notify::global::notification_system() else {
|
||||
let Some(ns) = rustfs_notify::notification_system() else {
|
||||
return Err(s3_error!(InternalError, "notification system not initialized"));
|
||||
};
|
||||
|
||||
@@ -401,7 +401,7 @@ impl Operation for RemoveNotificationTarget {
|
||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
|
||||
|
||||
// 3. Get notification system instance
|
||||
let Some(ns) = rustfs_notify::global::notification_system() else {
|
||||
let Some(ns) = rustfs_notify::notification_system() else {
|
||||
return Err(s3_error!(InternalError, "notification system not initialized"));
|
||||
};
|
||||
|
||||
|
||||
+2
-3
@@ -58,7 +58,7 @@ use rustfs_ecstore::{
|
||||
update_erasure_type,
|
||||
};
|
||||
use rustfs_iam::init_iam_sys;
|
||||
use rustfs_notify::global::notifier_instance;
|
||||
use rustfs_notify::notifier_global;
|
||||
use rustfs_obs::{init_obs, set_global_guard};
|
||||
use rustfs_targets::arn::TargetID;
|
||||
use rustfs_utils::net::parse_and_resolve_address;
|
||||
@@ -517,8 +517,7 @@ async fn add_bucket_notification_configuration(buckets: Vec<String>) {
|
||||
process_topic_configurations(&mut event_rules, cfg.topic_configurations.clone(), TargetID::from_str);
|
||||
process_lambda_configurations(&mut event_rules, cfg.lambda_function_configurations.clone(), TargetID::from_str);
|
||||
|
||||
if let Err(e) = notifier_instance()
|
||||
.add_event_specific_rules(bucket, region, &event_rules)
|
||||
if let Err(e) = notifier_global::add_event_specific_rules(bucket, region, &event_rules)
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "Failed to add rules: {e}"))
|
||||
{
|
||||
|
||||
+165
-361
@@ -14,6 +14,8 @@
|
||||
|
||||
use crate::auth::get_condition_values;
|
||||
use crate::error::ApiError;
|
||||
use crate::storage::entity;
|
||||
use crate::storage::helper::OperationHelper;
|
||||
use crate::storage::options::{filter_object_metadata, get_content_sha256};
|
||||
use crate::storage::{
|
||||
access::{ReqInfo, authorize_request},
|
||||
@@ -84,7 +86,7 @@ use rustfs_kms::{
|
||||
service_manager::get_global_encryption_service,
|
||||
types::{EncryptionMetadata, ObjectEncryptionContext},
|
||||
};
|
||||
use rustfs_notify::global::notifier_instance;
|
||||
use rustfs_notify::{EventArgsBuilder, notifier_global};
|
||||
use rustfs_policy::{
|
||||
auth,
|
||||
policy::{
|
||||
@@ -102,11 +104,10 @@ use rustfs_targets::{
|
||||
EventName,
|
||||
arn::{TargetID, TargetIDError},
|
||||
};
|
||||
use rustfs_utils::http::{AMZ_CHECKSUM_MODE, AMZ_CHECKSUM_TYPE};
|
||||
use rustfs_utils::{
|
||||
CompressionAlgorithm,
|
||||
CompressionAlgorithm, extract_req_params_header, extract_resp_elements, get_request_host, get_request_user_agent,
|
||||
http::{
|
||||
AMZ_BUCKET_REPLICATION_STATUS,
|
||||
AMZ_BUCKET_REPLICATION_STATUS, AMZ_CHECKSUM_MODE, AMZ_CHECKSUM_TYPE,
|
||||
headers::{
|
||||
AMZ_DECODED_CONTENT_LENGTH, AMZ_OBJECT_TAGGING, AMZ_RESTORE_EXPIRY_DAYS, AMZ_RESTORE_REQUEST_DATE,
|
||||
RESERVED_METADATA_PREFIX_LOWER,
|
||||
@@ -353,6 +354,7 @@ impl FS {
|
||||
}
|
||||
|
||||
async fn put_object_extract(&self, req: S3Request<PutObjectInput>) -> S3Result<S3Response<PutObjectOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::ObjectCreatedPut, "s3:PutObject").suppress_event();
|
||||
let input = req.input;
|
||||
|
||||
let PutObjectInput {
|
||||
@@ -495,20 +497,20 @@ impl FS {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
let event_args = rustfs_notify::EventArgs {
|
||||
event_name: EventName::ObjectCreatedPut,
|
||||
bucket_name: bucket.clone(),
|
||||
object: _obj_info.clone(),
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
req_params: extract_req_params_header(&req.headers),
|
||||
resp_elements: extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id: version_id.clone(),
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
host: get_request_host(&req.headers),
|
||||
user_agent: get_request_user_agent(&req.headers),
|
||||
};
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
notifier_global::notify(event_args).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -575,7 +577,9 @@ impl FS {
|
||||
checksum_crc64nvme,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,6 +606,7 @@ impl S3 for FS {
|
||||
fields(start_time=?time::OffsetDateTime::now_utc())
|
||||
)]
|
||||
async fn create_bucket(&self, req: S3Request<CreateBucketInput>) -> S3Result<S3Response<CreateBucketOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::BucketCreated, "s3:CreateBucket");
|
||||
let CreateBucketInput {
|
||||
bucket,
|
||||
object_lock_enabled_for_bucket,
|
||||
@@ -628,28 +633,15 @@ impl S3 for FS {
|
||||
|
||||
let output = CreateBucketOutput::default();
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::BucketCreated,
|
||||
bucket_name: bucket.clone(),
|
||||
object: ObjectInfo { ..Default::default() },
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id: String::new(),
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Copy an object from one location to another
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
async fn copy_object(&self, req: S3Request<CopyObjectInput>) -> S3Result<S3Response<CopyObjectOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedCopy, "s3:CopyObject");
|
||||
let CopyObjectInput {
|
||||
copy_source,
|
||||
bucket,
|
||||
@@ -830,28 +822,12 @@ impl S3 for FS {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper.object(object_info).version_id(version_id);
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedCopy,
|
||||
bucket_name: bucket.clone(),
|
||||
object: object_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
async fn restore_object(&self, req: S3Request<RestoreObjectInput>) -> S3Result<S3Response<RestoreObjectOutput>> {
|
||||
@@ -902,7 +878,7 @@ impl S3 for FS {
|
||||
}
|
||||
|
||||
//let mut api_err;
|
||||
let mut _status_code = http::StatusCode::OK;
|
||||
let mut _status_code = StatusCode::OK;
|
||||
let mut already_restored = false;
|
||||
if let Err(_err) = rreq.validate(store.clone()) {
|
||||
//api_err = to_api_err(ErrMalformedXML);
|
||||
@@ -919,7 +895,7 @@ impl S3 for FS {
|
||||
));
|
||||
}
|
||||
if !obj_info.restore_ongoing && obj_info.restore_expires.unwrap().unix_timestamp() != 0 {
|
||||
_status_code = http::StatusCode::ACCEPTED;
|
||||
_status_code = StatusCode::ACCEPTED;
|
||||
already_restored = true;
|
||||
}
|
||||
}
|
||||
@@ -1086,12 +1062,13 @@ impl S3 for FS {
|
||||
restore_output_path: None,
|
||||
};
|
||||
|
||||
return Ok(S3Response::with_headers(output, header));
|
||||
Ok(S3Response::with_headers(output, header))
|
||||
}
|
||||
|
||||
/// Delete a bucket
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
async fn delete_bucket(&self, req: S3Request<DeleteBucketInput>) -> S3Result<S3Response<DeleteBucketOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::BucketRemoved, "s3:DeleteBucket");
|
||||
let input = req.input;
|
||||
// TODO: DeleteBucketInput doesn't have force parameter?
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
@@ -1109,28 +1086,15 @@ impl S3 for FS {
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::BucketRemoved,
|
||||
bucket_name: input.bucket,
|
||||
object: ObjectInfo { ..Default::default() },
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(DeleteBucketOutput {})),
|
||||
version_id: String::new(),
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(DeleteBucketOutput {}))
|
||||
let result = Ok(S3Response::new(DeleteBucketOutput {}));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Delete an object
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
async fn delete_object(&self, mut req: S3Request<DeleteObjectInput>) -> S3Result<S3Response<DeleteObjectOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectRemovedDelete, "s3:DeleteObject");
|
||||
let DeleteObjectInput {
|
||||
bucket, key, version_id, ..
|
||||
} = req.input.clone();
|
||||
@@ -1233,32 +1197,20 @@ impl S3 for FS {
|
||||
EventName::ObjectRemovedDelete
|
||||
};
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name,
|
||||
bucket_name: bucket.clone(),
|
||||
object: ObjectInfo {
|
||||
name: key.clone(),
|
||||
bucket: bucket.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(DeleteBucketOutput {})),
|
||||
version_id: version_id.map(|v| v.to_string()).unwrap_or_default(),
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
helper = helper.event_name(event_name);
|
||||
helper = helper
|
||||
.object(obj_info)
|
||||
.version_id(version_id.map(|v| v.to_string()).unwrap_or_default());
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Delete multiple objects
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
async fn delete_objects(&self, req: S3Request<DeleteObjectsInput>) -> S3Result<S3Response<DeleteObjectsOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::ObjectRemovedDelete, "s3:DeleteObjects").suppress_event();
|
||||
let DeleteObjectsInput { bucket, delete, .. } = req.input;
|
||||
|
||||
if delete.objects.is_empty() || delete.objects.len() > 1000 {
|
||||
@@ -1393,10 +1345,12 @@ impl S3 for FS {
|
||||
.map(|v| v.as_ref().map(|v| v.clone().into()))
|
||||
.collect::<Vec<Option<DiskError>>>() as &[Option<DiskError>],
|
||||
) {
|
||||
return Err(S3Error::with_message(S3ErrorCode::NoSuchBucket, "Bucket not found".to_string()));
|
||||
let result = Err(S3Error::with_message(S3ErrorCode::NoSuchBucket, "Bucket not found".to_string()));
|
||||
let _ = helper.complete(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
for (i, err) in errs.into_iter().enumerate() {
|
||||
for (i, err) in errs.iter().enumerate() {
|
||||
let obj = dobjs[i].clone();
|
||||
|
||||
// let replication_state = obj.replication_state.clone().unwrap_or_default();
|
||||
@@ -1426,7 +1380,7 @@ impl S3 for FS {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(err) = err {
|
||||
if let Some(err) = err.clone() {
|
||||
delete_results[*didx].error = Some(Error {
|
||||
code: Some(err.to_string()),
|
||||
key: Some(object_to_delete[i].object_name.clone()),
|
||||
@@ -1481,39 +1435,39 @@ impl S3 for FS {
|
||||
}
|
||||
}
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
let req_headers = req.headers.clone();
|
||||
tokio::spawn(async move {
|
||||
for dobj in dobjs {
|
||||
let version_id = match dobj.version_id {
|
||||
None => String::new(),
|
||||
Some(v) => v.to_string(),
|
||||
};
|
||||
let mut event_name = EventName::ObjectRemovedDelete;
|
||||
if dobj.delete_marker {
|
||||
event_name = EventName::ObjectRemovedDeleteMarkerCreated;
|
||||
}
|
||||
for res in delete_results {
|
||||
if let Some(dobj) = res.delete_object {
|
||||
let event_name = if dobj.delete_marker {
|
||||
EventName::ObjectRemovedDeleteMarkerCreated
|
||||
} else {
|
||||
EventName::ObjectRemovedDelete
|
||||
};
|
||||
let event_args = EventArgsBuilder::new(
|
||||
event_name,
|
||||
bucket.clone(),
|
||||
ObjectInfo {
|
||||
name: dobj.object_name.clone(),
|
||||
bucket: bucket.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.version_id(dobj.version_id.map(|v| v.to_string()).unwrap_or_default())
|
||||
.req_params(extract_req_params_header(&req_headers))
|
||||
.resp_elements(extract_resp_elements(&S3Response::new(DeleteObjectsOutput::default())))
|
||||
.host(get_request_host(&req_headers))
|
||||
.user_agent(get_request_user_agent(&req_headers))
|
||||
.build();
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name,
|
||||
bucket_name: bucket.clone(),
|
||||
object: ObjectInfo {
|
||||
name: dobj.object_name,
|
||||
bucket: bucket.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(DeleteObjectsOutput {
|
||||
..Default::default()
|
||||
})),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
notifier_instance().notify(event_args).await;
|
||||
notifier_global::notify(event_args).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Get bucket location
|
||||
@@ -1548,6 +1502,7 @@ impl S3 for FS {
|
||||
fields(start_time=?time::OffsetDateTime::now_utc())
|
||||
)]
|
||||
async fn get_object(&self, req: S3Request<GetObjectInput>) -> S3Result<S3Response<GetObjectOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedGet, "s3:GetObject");
|
||||
// mc get 3
|
||||
|
||||
let GetObjectInput {
|
||||
@@ -1879,27 +1834,12 @@ impl S3 for FS {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
None => String::new(),
|
||||
Some(v) => v.to_string(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectAccessedGet,
|
||||
bucket_name: bucket.clone(),
|
||||
object: event_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(GetObjectOutput { ..Default::default() })),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper.object(event_info).version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
@@ -1921,6 +1861,7 @@ impl S3 for FS {
|
||||
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
async fn head_object(&self, req: S3Request<HeadObjectInput>) -> S3Result<S3Response<HeadObjectOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedHead, "s3:HeadObject");
|
||||
// mc get 2
|
||||
let HeadObjectInput {
|
||||
bucket,
|
||||
@@ -2055,27 +1996,13 @@ impl S3 for FS {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
None => String::new(),
|
||||
Some(v) => v.to_string(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectAccessedGet,
|
||||
bucket_name: bucket,
|
||||
object: event_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper.object(event_info).version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
result
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
@@ -2344,6 +2271,7 @@ impl S3 for FS {
|
||||
|
||||
// #[instrument(level = "debug", skip(self, req))]
|
||||
async fn put_object(&self, req: S3Request<PutObjectInput>) -> S3Result<S3Response<PutObjectOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::ObjectCreatedPut, "s3:PutObject");
|
||||
if req
|
||||
.headers
|
||||
.get("X-Amz-Meta-Snowball-Auto-Extract")
|
||||
@@ -2360,7 +2288,6 @@ impl S3 for FS {
|
||||
return Err(s3_error!(InvalidStorageClass));
|
||||
}
|
||||
}
|
||||
let event_version_id = input.version_id.as_ref().map(|v| v.to_string()).unwrap_or_default();
|
||||
let PutObjectInput {
|
||||
body,
|
||||
bucket,
|
||||
@@ -2612,7 +2539,6 @@ impl S3 for FS {
|
||||
.put_object(&bucket, &key, &mut reader, &opts)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
let event_info = obj_info.clone();
|
||||
let e_tag = obj_info.etag.clone().map(|etag| to_s3s_etag(&etag));
|
||||
|
||||
let repoptions =
|
||||
@@ -2671,23 +2597,9 @@ impl S3 for FS {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedPut,
|
||||
bucket_name: bucket.clone(),
|
||||
object: event_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id: event_version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
@@ -2695,6 +2607,7 @@ impl S3 for FS {
|
||||
&self,
|
||||
req: S3Request<CreateMultipartUploadInput>,
|
||||
) -> S3Result<S3Response<CreateMultipartUploadOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::ObjectCreatedPut, "s3:CreateMultipartUpload");
|
||||
let CreateMultipartUploadInput {
|
||||
bucket,
|
||||
key,
|
||||
@@ -2826,8 +2739,6 @@ impl S3 for FS {
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let object_name = key.clone();
|
||||
let bucket_name = bucket.clone();
|
||||
let output = CreateMultipartUploadOutput {
|
||||
bucket: Some(bucket),
|
||||
key: Some(key),
|
||||
@@ -2840,31 +2751,9 @@ impl S3 for FS {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedCompleteMultipartUpload,
|
||||
bucket_name: bucket_name.clone(),
|
||||
object: ObjectInfo {
|
||||
name: object_name,
|
||||
bucket: bucket_name,
|
||||
..Default::default()
|
||||
},
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
@@ -3430,6 +3319,7 @@ impl S3 for FS {
|
||||
&self,
|
||||
req: S3Request<CompleteMultipartUploadInput>,
|
||||
) -> S3Result<S3Response<CompleteMultipartUploadOutput>> {
|
||||
let helper = OperationHelper::new(&req, EventName::ObjectCreatedCompleteMultipartUpload, "s3:CompleteMultipartUpload");
|
||||
let input = req.input;
|
||||
let CompleteMultipartUploadInput {
|
||||
multipart_upload,
|
||||
@@ -3536,6 +3426,26 @@ impl S3 for FS {
|
||||
}
|
||||
|
||||
let output = CompleteMultipartUploadOutput {
|
||||
bucket: Some(bucket.clone()),
|
||||
key: Some(key.clone()),
|
||||
e_tag: obj_info.etag.clone().map(|etag| to_s3s_etag(&etag)),
|
||||
location: Some("us-east-1".to_string()),
|
||||
server_side_encryption: server_side_encryption.clone(), // TDD: Return encryption info
|
||||
ssekms_key_id: ssekms_key_id.clone(), // TDD: Return KMS key ID if present
|
||||
checksum_crc32: checksum_crc32.clone(),
|
||||
checksum_crc32c: checksum_crc32c.clone(),
|
||||
checksum_sha1: checksum_sha1.clone(),
|
||||
checksum_sha256: checksum_sha256.clone(),
|
||||
checksum_crc64nvme: checksum_crc64nvme.clone(),
|
||||
checksum_type: checksum_type.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
info!(
|
||||
"TDD: Created output: SSE={:?}, KMS={:?}",
|
||||
output.server_side_encryption, output.ssekms_key_id
|
||||
);
|
||||
|
||||
let helper_output = entity::CompleteMultipartUploadOutput {
|
||||
bucket: Some(bucket.clone()),
|
||||
key: Some(key.clone()),
|
||||
e_tag: obj_info.etag.clone().map(|etag| to_s3s_etag(&etag)),
|
||||
@@ -3550,10 +3460,6 @@ impl S3 for FS {
|
||||
checksum_type,
|
||||
..Default::default()
|
||||
};
|
||||
info!(
|
||||
"TDD: Created output: SSE={:?}, KMS={:?}",
|
||||
output.server_side_encryption, output.ssekms_key_id
|
||||
);
|
||||
|
||||
let mt2 = HashMap::new();
|
||||
let repoptions =
|
||||
@@ -3569,6 +3475,8 @@ impl S3 for FS {
|
||||
"TDD: About to return S3Response with output: SSE={:?}, KMS={:?}",
|
||||
output.server_side_encryption, output.ssekms_key_id
|
||||
);
|
||||
let helper_result = Ok(S3Response::new(helper_output));
|
||||
let _ = helper.complete(&helper_result);
|
||||
Ok(S3Response::new(output))
|
||||
}
|
||||
|
||||
@@ -3655,6 +3563,7 @@ impl S3 for FS {
|
||||
|
||||
#[instrument(level = "debug", skip(self, req))]
|
||||
async fn put_object_tagging(&self, req: S3Request<PutObjectTaggingInput>) -> S3Result<S3Response<PutObjectTaggingOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedPutTagging, "s3:PutObjectTagging");
|
||||
let PutObjectTaggingInput {
|
||||
bucket,
|
||||
key: object,
|
||||
@@ -3706,31 +3615,12 @@ impl S3 for FS {
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedPutTagging,
|
||||
bucket_name: bucket.clone(),
|
||||
object: ObjectInfo {
|
||||
name: object.clone(),
|
||||
bucket,
|
||||
..Default::default()
|
||||
},
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(PutObjectTaggingOutput { version_id: None })),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper.version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(PutObjectTaggingOutput { version_id: None }))
|
||||
let result = Ok(S3Response::new(PutObjectTaggingOutput { version_id: None }));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
@@ -3760,6 +3650,7 @@ impl S3 for FS {
|
||||
&self,
|
||||
req: S3Request<DeleteObjectTaggingInput>,
|
||||
) -> S3Result<S3Response<DeleteObjectTaggingOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedDeleteTagging, "s3:DeleteObjectTagging");
|
||||
let DeleteObjectTaggingInput { bucket, key: object, .. } = req.input.clone();
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
@@ -3773,31 +3664,12 @@ impl S3 for FS {
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => Uuid::new_v4().to_string(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedDeleteTagging,
|
||||
bucket_name: bucket.clone(),
|
||||
object: ObjectInfo {
|
||||
name: object.clone(),
|
||||
bucket,
|
||||
..Default::default()
|
||||
},
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(DeleteObjectTaggingOutput { version_id: None })),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
helper = helper.version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(DeleteObjectTaggingOutput { version_id: None }))
|
||||
let result = Ok(S3Response::new(DeleteObjectTaggingOutput { version_id: None }));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
@@ -4391,7 +4263,7 @@ impl S3 for FS {
|
||||
let region = rustfs_ecstore::global::get_global_region().unwrap_or_else(|| req.region.clone().unwrap_or_default());
|
||||
|
||||
// Purge old rules and resolve new rules in parallel
|
||||
let clear_rules = notifier_instance().clear_bucket_notification_rules(&bucket);
|
||||
let clear_rules = notifier_global::clear_bucket_notification_rules(&bucket);
|
||||
let parse_rules = async {
|
||||
let mut event_rules = Vec::new();
|
||||
|
||||
@@ -4419,8 +4291,7 @@ impl S3 for FS {
|
||||
clear_result.map_err(|e| s3_error!(InternalError, "Failed to clear rules: {e}"))?;
|
||||
|
||||
// Add a new notification rule
|
||||
notifier_instance()
|
||||
.add_event_specific_rules(&bucket, ®ion, &event_rules)
|
||||
notifier_global::add_event_specific_rules(&bucket, ®ion, &event_rules)
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "Failed to add rules: {e}"))?;
|
||||
|
||||
@@ -4532,6 +4403,7 @@ impl S3 for FS {
|
||||
&self,
|
||||
req: S3Request<GetObjectAttributesInput>,
|
||||
) -> S3Result<S3Response<GetObjectAttributesOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedAttributes, "s3:GetObjectAttributes");
|
||||
let GetObjectAttributesInput { bucket, key, .. } = req.input.clone();
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
@@ -4550,31 +4422,19 @@ impl S3 for FS {
|
||||
object_parts: None,
|
||||
..Default::default()
|
||||
};
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectAccessedAttributes,
|
||||
bucket_name: bucket.clone(),
|
||||
object: ObjectInfo {
|
||||
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper
|
||||
.object(ObjectInfo {
|
||||
name: key.clone(),
|
||||
bucket,
|
||||
..Default::default()
|
||||
},
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
})
|
||||
.version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
async fn put_object_acl(&self, req: S3Request<PutObjectAclInput>) -> S3Result<S3Response<PutObjectAclOutput>> {
|
||||
@@ -4690,6 +4550,7 @@ impl S3 for FS {
|
||||
&self,
|
||||
req: S3Request<GetObjectLegalHoldInput>,
|
||||
) -> S3Result<S3Response<GetObjectLegalHoldOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedGetLegalHold, "s3:GetObjectLegalHold");
|
||||
let GetObjectLegalHoldInput {
|
||||
bucket, key, version_id, ..
|
||||
} = req.input.clone();
|
||||
@@ -4732,33 +4593,19 @@ impl S3 for FS {
|
||||
}),
|
||||
};
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => Uuid::new_v4().to_string(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectAccessedGetLegalHold,
|
||||
bucket_name: bucket.clone(),
|
||||
object: object_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
helper = helper.object(object_info).version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
async fn put_object_legal_hold(
|
||||
&self,
|
||||
req: S3Request<PutObjectLegalHoldInput>,
|
||||
) -> S3Result<S3Response<PutObjectLegalHoldOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedPutLegalHold, "s3:PutObjectLegalHold");
|
||||
let PutObjectLegalHoldInput {
|
||||
bucket,
|
||||
key,
|
||||
@@ -4811,33 +4658,19 @@ impl S3 for FS {
|
||||
let output = PutObjectLegalHoldOutput {
|
||||
request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)),
|
||||
};
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedPutLegalHold,
|
||||
bucket_name: bucket.clone(),
|
||||
object: info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper.object(info).version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
async fn get_object_retention(
|
||||
&self,
|
||||
req: S3Request<GetObjectRetentionInput>,
|
||||
) -> S3Result<S3Response<GetObjectRetentionOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedGetRetention, "s3:GetObjectRetention");
|
||||
let GetObjectRetentionInput {
|
||||
bucket, key, version_id, ..
|
||||
} = req.input.clone();
|
||||
@@ -4872,33 +4705,19 @@ impl S3 for FS {
|
||||
let output = GetObjectRetentionOutput {
|
||||
retention: Some(ObjectLockRetention { mode, retain_until_date }),
|
||||
};
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectAccessedGetRetention,
|
||||
bucket_name: bucket.clone(),
|
||||
object: object_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||
helper = helper.object(object_info).version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
|
||||
async fn put_object_retention(
|
||||
&self,
|
||||
req: S3Request<PutObjectRetentionInput>,
|
||||
) -> S3Result<S3Response<PutObjectRetentionOutput>> {
|
||||
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedPutRetention, "s3:PutObjectRetention");
|
||||
let PutObjectRetentionInput {
|
||||
bucket,
|
||||
key,
|
||||
@@ -4947,27 +4766,12 @@ impl S3 for FS {
|
||||
request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)),
|
||||
};
|
||||
|
||||
let version_id = match req.input.version_id {
|
||||
Some(v) => v.to_string(),
|
||||
None => Uuid::new_v4().to_string(),
|
||||
};
|
||||
let event_args = rustfs_notify::event::EventArgs {
|
||||
event_name: EventName::ObjectCreatedPutRetention,
|
||||
bucket_name: bucket.clone(),
|
||||
object: object_info,
|
||||
req_params: rustfs_utils::extract_req_params_header(&req.headers),
|
||||
resp_elements: rustfs_utils::extract_resp_elements(&S3Response::new(output.clone())),
|
||||
version_id,
|
||||
host: rustfs_utils::get_request_host(&req.headers),
|
||||
user_agent: rustfs_utils::get_request_user_agent(&req.headers),
|
||||
};
|
||||
let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
helper = helper.object(object_info).version_id(version_id);
|
||||
|
||||
// Asynchronous call will not block the response of the current request
|
||||
tokio::spawn(async move {
|
||||
notifier_instance().notify(event_args).await;
|
||||
});
|
||||
|
||||
Ok(S3Response::new(output))
|
||||
let result = Ok(S3Response::new(output));
|
||||
let _ = helper.complete(&result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 RustFS Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use s3s::dto::{
|
||||
BucketKeyEnabled, BucketName, ChecksumCRC32, ChecksumCRC32C, ChecksumCRC64NVME, ChecksumSHA1, ChecksumSHA256, ChecksumType,
|
||||
ETag, Expiration, Location, ObjectKey, ObjectVersionId, RequestCharged, SSEKMSKeyId, ServerSideEncryption,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CompleteMultipartUploadOutput {
|
||||
pub bucket: Option<BucketName>,
|
||||
pub bucket_key_enabled: Option<BucketKeyEnabled>,
|
||||
pub checksum_crc32: Option<ChecksumCRC32>,
|
||||
pub checksum_crc32c: Option<ChecksumCRC32C>,
|
||||
pub checksum_crc64nvme: Option<ChecksumCRC64NVME>,
|
||||
pub checksum_sha1: Option<ChecksumSHA1>,
|
||||
pub checksum_sha256: Option<ChecksumSHA256>,
|
||||
pub checksum_type: Option<ChecksumType>,
|
||||
pub e_tag: Option<ETag>,
|
||||
pub expiration: Option<Expiration>,
|
||||
pub key: Option<ObjectKey>,
|
||||
pub location: Option<Location>,
|
||||
pub request_charged: Option<RequestCharged>,
|
||||
pub ssekms_key_id: Option<SSEKMSKeyId>,
|
||||
pub server_side_encryption: Option<ServerSideEncryption>,
|
||||
pub version_id: Option<ObjectVersionId>,
|
||||
}
|
||||
|
||||
impl From<s3s::dto::CompleteMultipartUploadOutput> for CompleteMultipartUploadOutput {
|
||||
fn from(output: s3s::dto::CompleteMultipartUploadOutput) -> Self {
|
||||
Self {
|
||||
bucket: output.bucket,
|
||||
bucket_key_enabled: output.bucket_key_enabled,
|
||||
checksum_crc32: output.checksum_crc32,
|
||||
checksum_crc32c: output.checksum_crc32c,
|
||||
checksum_crc64nvme: output.checksum_crc64nvme,
|
||||
checksum_sha1: output.checksum_sha1,
|
||||
checksum_sha256: output.checksum_sha256,
|
||||
checksum_type: output.checksum_type,
|
||||
e_tag: output.e_tag,
|
||||
expiration: output.expiration,
|
||||
key: output.key,
|
||||
location: output.location,
|
||||
request_charged: output.request_charged,
|
||||
ssekms_key_id: output.ssekms_key_id,
|
||||
server_side_encryption: output.server_side_encryption,
|
||||
version_id: output.version_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2024 RustFS Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use http::StatusCode;
|
||||
use rustfs_audit::{
|
||||
entity::{ApiDetails, ApiDetailsBuilder, AuditEntryBuilder},
|
||||
global::AuditLogger,
|
||||
};
|
||||
use rustfs_ecstore::store_api::ObjectInfo;
|
||||
use rustfs_notify::{EventArgsBuilder, notifier_global};
|
||||
use rustfs_targets::EventName;
|
||||
use rustfs_utils::{
|
||||
extract_req_params, extract_req_params_header, extract_resp_elements, get_request_host, get_request_user_agent,
|
||||
};
|
||||
use s3s::{S3Request, S3Response, S3Result};
|
||||
use std::future::Future;
|
||||
use tokio::runtime::{Builder, Handle};
|
||||
|
||||
/// Schedules an asynchronous task on the current runtime;
|
||||
/// if there is no runtime, creates a minimal runtime execution on a new thread.
|
||||
fn spawn_background<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
if let Ok(handle) = Handle::try_current() {
|
||||
drop(handle.spawn(fut));
|
||||
} else {
|
||||
std::thread::spawn(|| {
|
||||
if let Ok(rt) = Builder::new_current_thread().enable_all().build() {
|
||||
rt.block_on(fut);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A unified helper structure for building and distributing audit logs and event notifications via RAII mode at the end of an S3 operation scope.
|
||||
pub struct OperationHelper {
|
||||
audit_builder: Option<AuditEntryBuilder>,
|
||||
api_builder: ApiDetailsBuilder,
|
||||
event_builder: Option<EventArgsBuilder>,
|
||||
start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl OperationHelper {
|
||||
/// Create a new OperationHelper for S3 requests.
|
||||
pub fn new(req: &S3Request<impl Send + Sync>, event: EventName, trigger: &'static str) -> Self {
|
||||
// Parse path -> bucket/object
|
||||
let path = req.uri.path().trim_start_matches('/');
|
||||
let mut segs = path.splitn(2, '/');
|
||||
let bucket = segs.next().unwrap_or("").to_string();
|
||||
let object_key = segs.next().unwrap_or("").to_string();
|
||||
|
||||
// Infer remote address
|
||||
let remote_host = req
|
||||
.headers
|
||||
.get("x-forwarded-for")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.or_else(|| req.headers.get("x-real-ip").and_then(|v| v.to_str().ok()))
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
// Initialize audit builder
|
||||
let mut api_builder = ApiDetailsBuilder::new().name(trigger);
|
||||
if !bucket.is_empty() {
|
||||
api_builder = api_builder.bucket(&bucket);
|
||||
}
|
||||
if !object_key.is_empty() {
|
||||
api_builder = api_builder.object(&object_key);
|
||||
}
|
||||
// Audit builder
|
||||
let mut audit_builder = AuditEntryBuilder::new("1.0", event, trigger, ApiDetails::default())
|
||||
.remote_host(remote_host)
|
||||
.user_agent(get_request_user_agent(&req.headers))
|
||||
.req_host(get_request_host(&req.headers))
|
||||
.req_path(req.uri.path().to_string())
|
||||
.req_query(extract_req_params(req));
|
||||
|
||||
if let Some(req_id) = req.headers.get("x-amz-request-id") {
|
||||
if let Ok(id_str) = req_id.to_str() {
|
||||
audit_builder = audit_builder.request_id(id_str);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize event builder
|
||||
// object is a placeholder that must be set later using the `object()` method.
|
||||
let event_builder = EventArgsBuilder::new(event, bucket, ObjectInfo::default())
|
||||
.host(get_request_host(&req.headers))
|
||||
.user_agent(get_request_user_agent(&req.headers))
|
||||
.req_params(extract_req_params_header(&req.headers));
|
||||
|
||||
Self {
|
||||
audit_builder: Some(audit_builder),
|
||||
api_builder,
|
||||
event_builder: Some(event_builder),
|
||||
start_time: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the ObjectInfo for event notification.
|
||||
pub fn object(mut self, object_info: ObjectInfo) -> Self {
|
||||
if let Some(builder) = self.event_builder.take() {
|
||||
self.event_builder = Some(builder.object(object_info));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the version ID for event notifications.
|
||||
pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
|
||||
if let Some(builder) = self.event_builder.take() {
|
||||
self.event_builder = Some(builder.version_id(version_id));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the event name for event notifications.
|
||||
pub fn event_name(mut self, event_name: EventName) -> Self {
|
||||
if let Some(builder) = self.event_builder.take() {
|
||||
self.event_builder = Some(builder.event_name(event_name));
|
||||
}
|
||||
|
||||
if let Some(builder) = self.audit_builder.take() {
|
||||
self.audit_builder = Some(builder.event(event_name));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Complete operational details from S3 results.
|
||||
/// This method should be called immediately before the function returns.
|
||||
/// It consumes and prepares auxiliary structures for use during `drop`.
|
||||
pub fn complete(mut self, result: &S3Result<S3Response<impl Send + Sync>>) -> Self {
|
||||
// Complete audit log
|
||||
if let Some(builder) = self.audit_builder.take() {
|
||||
let (status, status_code, error_msg) = match result {
|
||||
Ok(res) => ("success".to_string(), res.status.unwrap_or(StatusCode::OK).as_u16() as i32, None),
|
||||
Err(e) => (
|
||||
"failure".to_string(),
|
||||
e.status_code().unwrap_or(StatusCode::BAD_REQUEST).as_u16() as i32,
|
||||
e.message().map(|s| s.to_string()),
|
||||
),
|
||||
};
|
||||
|
||||
let ttr = self.start_time.elapsed();
|
||||
let api_details = self
|
||||
.api_builder
|
||||
.clone()
|
||||
.status(status)
|
||||
.status_code(status_code)
|
||||
.time_to_response(format!("{:.2?}", ttr))
|
||||
.time_to_response_in_ns(ttr.as_nanos().to_string())
|
||||
.build();
|
||||
|
||||
let mut final_builder = builder.api(api_details.clone());
|
||||
if let Some(err) = error_msg {
|
||||
final_builder = final_builder.error(err);
|
||||
}
|
||||
self.audit_builder = Some(final_builder);
|
||||
self.api_builder = ApiDetailsBuilder(api_details); // Store final details for Drop use
|
||||
}
|
||||
|
||||
// Completion event notification (only on success)
|
||||
if let (Some(builder), Ok(res)) = (self.event_builder.take(), result) {
|
||||
self.event_builder = Some(builder.resp_elements(extract_resp_elements(res)));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Suppresses the automatic event notification on drop.
|
||||
pub fn suppress_event(mut self) -> Self {
|
||||
self.event_builder = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OperationHelper {
|
||||
fn drop(&mut self) {
|
||||
// Distribute audit logs
|
||||
if let Some(builder) = self.audit_builder.take() {
|
||||
spawn_background(async move {
|
||||
AuditLogger::log(builder.build()).await;
|
||||
});
|
||||
}
|
||||
|
||||
// Distribute event notification (only on success)
|
||||
if self.api_builder.0.status.as_deref() == Some("success") {
|
||||
if let Some(builder) = self.event_builder.take() {
|
||||
let event_args = builder.build();
|
||||
// Avoid generating notifications for copy requests
|
||||
if !event_args.is_replication_request() {
|
||||
spawn_background(async move {
|
||||
notifier_global::notify(event_args).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
pub mod access;
|
||||
pub mod ecfs;
|
||||
// pub mod error;
|
||||
pub(crate) mod entity;
|
||||
pub(crate) mod helper;
|
||||
pub mod options;
|
||||
pub mod tonic_service;
|
||||
|
||||
Reference in New Issue
Block a user