mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 00:38:16 +00:00
feat(admin): add MinIO-compatible admin aliases (#2307)
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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, ¶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");
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user