feat(admin): add MinIO-compatible admin aliases (#2307)

This commit is contained in:
安正超
2026-03-28 08:19:56 +08:00
committed by GitHub
parent 5e21c398f5
commit c20f2555eb
5 changed files with 114 additions and 15 deletions
+12
View File
@@ -81,12 +81,24 @@ pub fn register_bucket_meta_route(r: &mut S3Router<AdminOperation>) -> 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(())
}
+26 -2
View File
@@ -102,6 +102,13 @@ fn extract_query_params(uri: &hyper::Uri) -> HashMap<String, String> {
params
}
fn extract_key_id(uri: &hyper::Uri) -> Option<String> {
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<std::sync::Arc<rustfs_kms::KmsServiceManager>> {
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 {}
@@ -71,12 +71,24 @@ pub fn register_kms_management_route(r: &mut S3Router<AdminOperation>) -> 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<AdminOperation>) -> 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(),
+51 -13
View File
@@ -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<String> {
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<AdminOperation>) -> std::io::Result<()> {
r.insert(
Method::GET,
@@ -94,6 +110,12 @@ pub fn register_tier_route(r: &mut S3Router<AdminOperation>) -> 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<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
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<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
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, &params)?;
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, &params).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");
@@ -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"));