diff --git a/rustfs/src/admin/handlers/bucket_meta.rs b/rustfs/src/admin/handlers/bucket_meta.rs index 10ac47356..39b938753 100644 --- a/rustfs/src/admin/handlers/bucket_meta.rs +++ b/rustfs/src/admin/handlers/bucket_meta.rs @@ -81,12 +81,24 @@ pub fn register_bucket_meta_route(r: &mut S3Router) -> std::io:: AdminOperation(&ExportBucketMetadata {}), )?; + r.insert( + Method::GET, + format!("{}{}", ADMIN_PREFIX, "/v3/export-bucket-metadata").as_str(), + AdminOperation(&ExportBucketMetadata {}), + )?; + r.insert( Method::PUT, format!("{}{}", ADMIN_PREFIX, "/import-bucket-metadata").as_str(), AdminOperation(&ImportBucketMetadata {}), )?; + r.insert( + Method::PUT, + format!("{}{}", ADMIN_PREFIX, "/v3/import-bucket-metadata").as_str(), + AdminOperation(&ImportBucketMetadata {}), + )?; + Ok(()) } diff --git a/rustfs/src/admin/handlers/kms_keys.rs b/rustfs/src/admin/handlers/kms_keys.rs index 2351a698e..bee73bc6a 100644 --- a/rustfs/src/admin/handlers/kms_keys.rs +++ b/rustfs/src/admin/handlers/kms_keys.rs @@ -102,6 +102,13 @@ fn extract_query_params(uri: &hyper::Uri) -> HashMap { params } +fn extract_key_id(uri: &hyper::Uri) -> Option { + let query_params = extract_query_params(uri); + ["keyId", "key-id", "key"] + .into_iter() + .find_map(|name| query_params.get(name).filter(|value| !value.is_empty()).cloned()) +} + fn kms_service_manager_from_context() -> Option> { resolve_kms_runtime_service_manager() } @@ -247,8 +254,7 @@ impl Operation for DescribeKeyHandler { ) .await?; - let query_params = extract_query_params(&req.uri); - let Some(key_id) = query_params.get("keyId") else { + let Some(key_id) = extract_key_id(&req.uri) else { return Err(s3_error!(InvalidRequest, "missing keyId parameter")); }; @@ -280,6 +286,24 @@ impl Operation for DescribeKeyHandler { } } +#[cfg(test)] +mod tests { + use super::extract_key_id; + use http::Uri; + + #[test] + fn test_extract_key_id_supports_minio_aliases() { + for (uri, expected) in [ + ("/rustfs/admin/v3/kms/key/status?keyId=legacy-key", "legacy-key"), + ("/rustfs/admin/v3/kms/key/status?key-id=minio-key", "minio-key"), + ("/rustfs/admin/v3/kms/key/status?key=fallback-key", "fallback-key"), + ] { + let uri: Uri = uri.parse().expect("uri should parse"); + assert_eq!(extract_key_id(&uri).as_deref(), Some(expected)); + } + } +} + /// List KMS keys (legacy endpoint) pub struct ListKeysHandler {} diff --git a/rustfs/src/admin/handlers/kms_management.rs b/rustfs/src/admin/handlers/kms_management.rs index 89769946d..c91c4644f 100644 --- a/rustfs/src/admin/handlers/kms_management.rs +++ b/rustfs/src/admin/handlers/kms_management.rs @@ -71,12 +71,24 @@ pub fn register_kms_management_route(r: &mut S3Router) -> std::i AdminOperation(&CreateKeyHandler {}), )?; + r.insert( + Method::POST, + format!("{}{}", ADMIN_PREFIX, "/v3/kms/key/create").as_str(), + AdminOperation(&CreateKeyHandler {}), + )?; + r.insert( Method::GET, format!("{}{}", ADMIN_PREFIX, "/v3/kms/describe-key").as_str(), AdminOperation(&DescribeKeyHandler {}), )?; + r.insert( + Method::GET, + format!("{}{}", ADMIN_PREFIX, "/v3/kms/key/status").as_str(), + AdminOperation(&DescribeKeyHandler {}), + )?; + r.insert( Method::GET, format!("{}{}", ADMIN_PREFIX, "/v3/kms/list-keys").as_str(), @@ -95,6 +107,12 @@ pub fn register_kms_management_route(r: &mut S3Router) -> std::i AdminOperation(&KmsStatusHandler {}), )?; + r.insert( + Method::POST, + format!("{}{}", ADMIN_PREFIX, "/v3/kms/status").as_str(), + AdminOperation(&KmsStatusHandler {}), + )?; + r.insert( Method::GET, format!("{}{}", ADMIN_PREFIX, "/v3/kms/config").as_str(), diff --git a/rustfs/src/admin/handlers/tier.rs b/rustfs/src/admin/handlers/tier.rs index f272e3444..11b61760f 100644 --- a/rustfs/src/admin/handlers/tier.rs +++ b/rustfs/src/admin/handlers/tier.rs @@ -22,6 +22,7 @@ use crate::{ auth::{check_key_valid, get_session_token}, server::{ADMIN_PREFIX, RemoteAddr}, }; +use http::Uri; use http::{HeaderMap, StatusCode}; use hyper::Method; use matchit::Params; @@ -81,6 +82,21 @@ pub struct AddTierQuery { pub struct AddTier {} +fn resolve_tier_name(uri: &Uri, params: &Params<'_, '_>) -> S3Result { + if let Some(tier) = params.get("tier").map(str::trim).filter(|tier| !tier.is_empty()) { + return Ok(tier.to_string()); + } + + let query = if let Some(query) = uri.query() { + let input: AddTierQuery = from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get query failed"))?; + input + } else { + AddTierQuery::default() + }; + + Ok(require_tier_name(&query)?.to_string()) +} + pub fn register_tier_route(r: &mut S3Router) -> std::io::Result<()> { r.insert( Method::GET, @@ -94,6 +110,12 @@ pub fn register_tier_route(r: &mut S3Router) -> std::io::Result< AdminOperation(&GetTierInfo {}), )?; + r.insert( + Method::GET, + format!("{}{}", ADMIN_PREFIX, "/v3/tier/{tier}").as_str(), + AdminOperation(&VerifyTier {}), + )?; + r.insert( Method::DELETE, format!("{}{}", ADMIN_PREFIX, "/v3/tier/{tiername}").as_str(), @@ -461,17 +483,7 @@ impl Operation for RemoveTier { pub struct VerifyTier {} #[async_trait::async_trait] impl Operation for VerifyTier { - async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { - let query = { - if let Some(query) = req.uri.query() { - let input: AddTierQuery = - from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get query failed"))?; - input - } else { - AddTierQuery::default() - } - }; - + async fn call(&self, req: S3Request, params: Params<'_, '_>) -> S3Result> { let Some(input_cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")); }; @@ -489,10 +501,10 @@ impl Operation for VerifyTier { ) .await?; - let tier_name = require_tier_name(&query)?; + let tier = resolve_tier_name(&req.uri, ¶ms)?; let tier_config_mgr_handle = resolve_tier_config_handle(); let mut tier_config_mgr = tier_config_mgr_handle.write().await; - tier_config_mgr.verify(tier_name).await.map_err(map_tier_verify_error)?; + tier_config_mgr.verify(&tier).await.map_err(map_tier_verify_error)?; let mut header = HeaderMap::new(); header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); @@ -672,8 +684,34 @@ impl Operation for ClearTier { #[cfg(test)] mod tests { use super::*; + use http::Uri; + use matchit::Router; use rustfs_ecstore::bucket::lifecycle::tier_last_day_stats::LastDayTierStats; + #[test] + fn resolve_tier_name_prefers_path_parameter() { + let uri: Uri = "/rustfs/admin/v3/tier/HOT?tier=COLD".parse().expect("uri should parse"); + let mut router = Router::new(); + router + .insert("/rustfs/admin/v3/tier/{tier}", ()) + .expect("route should insert"); + let matched = router.at("/rustfs/admin/v3/tier/HOT").expect("route should match"); + + let tier = resolve_tier_name(&uri, &matched.params).expect("path parameter should resolve"); + assert_eq!(tier, "HOT"); + } + + #[test] + fn resolve_tier_name_falls_back_to_query_parameter() { + let uri: Uri = "/rustfs/admin/v3/tier-stats?tier=WARM".parse().expect("uri should parse"); + let mut router: Router<()> = Router::new(); + router.insert("/", ()).expect("root route should insert"); + let params = router.at("/").expect("root route should match").params; + + let tier = resolve_tier_name(&uri, ¶ms).expect("query parameter should resolve"); + assert_eq!(tier, "WARM"); + } + #[test] fn require_tier_name_rejects_missing_value() { let err = require_tier_name(&AddTierQuery::default()).expect_err("missing tier should return an error"); diff --git a/rustfs/src/admin/route_registration_test.rs b/rustfs/src/admin/route_registration_test.rs index c6821e5ac..8682bd753 100644 --- a/rustfs/src/admin/route_registration_test.rs +++ b/rustfs/src/admin/route_registration_test.rs @@ -100,6 +100,7 @@ fn test_register_routes_cover_representative_admin_paths() { assert_route(&router, Method::POST, &admin_path("/v3/background-heal/status")); assert_route(&router, Method::GET, &admin_path("/v3/tier")); + assert_route(&router, Method::GET, &admin_path("/v3/tier/HOT")); assert_route(&router, Method::POST, &admin_path("/v3/tier/clear")); assert_route(&router, Method::PUT, &admin_path("/v3/set-bucket-quota")); assert_route(&router, Method::GET, &admin_path("/v3/get-bucket-quota")); @@ -107,13 +108,19 @@ fn test_register_routes_cover_representative_admin_paths() { assert_route(&router, Method::GET, &admin_path("/v3/quota-stats/test-bucket")); assert_route(&router, Method::GET, &admin_path("/export-bucket-metadata")); + assert_route(&router, Method::GET, &admin_path("/v3/export-bucket-metadata")); assert_route(&router, Method::PUT, &admin_path("/import-bucket-metadata")); + assert_route(&router, Method::PUT, &admin_path("/v3/import-bucket-metadata")); assert_route(&router, Method::GET, &admin_path("/v3/list-remote-targets")); assert_route(&router, Method::PUT, &admin_path("/v3/set-remote-target")); assert_route(&router, Method::GET, &admin_path("/debug/pprof/profile")); assert_route(&router, Method::POST, &admin_path("/v3/kms/create-key")); + assert_route(&router, Method::POST, &admin_path("/v3/kms/key/create")); assert_route(&router, Method::POST, &admin_path("/v3/kms/configure")); + assert_route(&router, Method::GET, &admin_path("/v3/kms/status")); + assert_route(&router, Method::POST, &admin_path("/v3/kms/status")); + assert_route(&router, Method::GET, &admin_path("/v3/kms/key/status")); assert_route(&router, Method::POST, &admin_path("/v3/kms/keys")); assert_route(&router, Method::GET, &admin_path("/v3/kms/keys")); assert_route(&router, Method::GET, &admin_path("/v3/kms/keys/test-key"));