From 2dfad181a088368c903fcd2f9e1f19e2eeb664c8 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Thu, 9 Jul 2026 04:52:52 +0800 Subject: [PATCH] fix(admin): include accountinfo bucket quota Expose accountinfo bucket quota only when a bucket quota is configured while preserving the existing response shape for buckets without quotas. --- crates/madmin/src/user.rs | 18 ++++++++++++++++++ rustfs/src/admin/handlers/account_info.rs | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/madmin/src/user.rs b/crates/madmin/src/user.rs index d7ef97566..114dad6e6 100644 --- a/crates/madmin/src/user.rs +++ b/crates/madmin/src/user.rs @@ -423,6 +423,8 @@ pub struct BucketDetails { pub versioning_suspended: bool, pub locking: bool, pub replication: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub quota: Option, // pub tagging: Option, } @@ -1036,6 +1038,7 @@ mod tests { versioning_suspended: false, locking: true, replication: false, + quota: None, }), prefix_usage, created: Some(now), @@ -1064,12 +1067,27 @@ mod tests { versioning_suspended: false, locking: true, replication: true, + quota: Some(1024), }; assert!(details.versioning); assert!(!details.versioning_suspended); assert!(details.locking); assert!(details.replication); + assert_eq!(details.quota, Some(1024)); + } + + #[test] + fn bucket_details_serializes_quota_only_when_configured() { + let with_quota = BucketDetails { + quota: Some(1024), + ..Default::default() + }; + let value = serde_json::to_value(with_quota).expect("bucket details should serialize"); + assert_eq!(value["quota"], 1024); + + let without_quota = serde_json::to_value(BucketDetails::default()).expect("bucket details should serialize"); + assert!(without_quota.get("quota").is_none()); } #[test] diff --git a/rustfs/src/admin/handlers/account_info.rs b/rustfs/src/admin/handlers/account_info.rs index cbaa0a414..73f923568 100644 --- a/rustfs/src/admin/handlers/account_info.rs +++ b/rustfs/src/admin/handlers/account_info.rs @@ -104,6 +104,13 @@ async fn bucket_replication_enabled(bucket: &str) -> bool { .is_some_and(|(config, _)| replication_config_enabled(&config)) } +async fn bucket_quota_limit(bucket: &str) -> Option { + metadata_sys::get_quota_config(bucket) + .await + .ok() + .and_then(|(config, _)| config.get_quota_limit()) +} + #[async_trait::async_trait] impl Operation for AccountInfoHandler { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { @@ -258,7 +265,6 @@ impl Operation for AccountInfoHandler { for bucket in buckets.iter() { let (rd, wr) = is_allow(bucket.name.clone()).await; if rd || wr { - // TODO: BucketQuotaSys let mut bucket_info = rustfs_madmin::BucketAccessInfo { name: bucket.name.clone(), details: Some(rustfs_madmin::BucketDetails { @@ -266,6 +272,7 @@ impl Operation for AccountInfoHandler { versioning_suspended: BucketVersioningSys::suspended(bucket.name.as_str()).await, locking: bucket_locking_enabled(bucket.name.as_str()).await, replication: bucket_replication_enabled(bucket.name.as_str()).await, + quota: bucket_quota_limit(bucket.name.as_str()).await, }), created: bucket.created, access: rustfs_madmin::AccountAccess { read: rd, write: wr },