aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <lx@deuxfleurs.fr>2025-01-28 15:44:14 +0100
committerAlex Auvolat <lx@deuxfleurs.fr>2025-01-29 19:26:16 +0100
commitaf1a53083452e7953736261db57aea4a68aa4278 (patch)
treef28c1a8b8f70b532ad1f3234a567d6bc5abac537
parentc99bfe69ea19497895d32669fd15c689b86035d8 (diff)
downloadgarage-af1a53083452e7953736261db57aea4a68aa4278.tar.gz
garage-af1a53083452e7953736261db57aea4a68aa4278.zip
admin api: refactor using macro
-rw-r--r--src/api/admin/api.rs174
-rw-r--r--src/api/admin/api_server.rs18
-rw-r--r--src/api/admin/macros.rs58
-rw-r--r--src/api/admin/mod.rs1
-rw-r--r--src/api/admin/router_v2.rs2
-rw-r--r--src/api/generic_server.rs2
-rw-r--r--src/api/k2v/api_server.rs4
-rw-r--r--src/api/s3/api_server.rs4
8 files changed, 113 insertions, 150 deletions
diff --git a/src/api/admin/api.rs b/src/api/admin/api.rs
index b0ab058a..c8fad95b 100644
--- a/src/api/admin/api.rs
+++ b/src/api/admin/api.rs
@@ -2,161 +2,63 @@ use std::net::SocketAddr;
use std::sync::Arc;
use async_trait::async_trait;
+use paste::paste;
use serde::{Deserialize, Serialize};
use garage_model::garage::Garage;
use crate::admin::error::Error;
+use crate::admin::macros::*;
use crate::admin::EndpointHandler;
use crate::helpers::is_default;
-pub enum AdminApiRequest {
+// This generates the following:
+// - An enum AdminApiRequest that contains a variant for all endpoints
+// - An enum AdminApiResponse that contains a variant for all non-special endpoints
+// - AdminApiRequest::name() that returns the name of the endpoint
+// - impl EndpointHandler for AdminApiHandler, that uses the impl EndpointHandler
+// of each request type below for non-special endpoints
+admin_endpoints![
// Special endpoints of the Admin API
- Options(OptionsRequest),
- CheckDomain(CheckDomainRequest),
- Health(HealthRequest),
- Metrics(MetricsRequest),
+ @special Options,
+ @special CheckDomain,
+ @special Health,
+ @special Metrics,
// Cluster operations
- GetClusterStatus(GetClusterStatusRequest),
- GetClusterHealth(GetClusterHealthRequest),
- ConnectClusterNodes(ConnectClusterNodesRequest),
- GetClusterLayout(GetClusterLayoutRequest),
- UpdateClusterLayout(UpdateClusterLayoutRequest),
- ApplyClusterLayout(ApplyClusterLayoutRequest),
- RevertClusterLayout(RevertClusterLayoutRequest),
+ GetClusterStatus,
+ GetClusterHealth,
+ ConnectClusterNodes,
+ GetClusterLayout,
+ UpdateClusterLayout,
+ ApplyClusterLayout,
+ RevertClusterLayout,
// Access key operations
- ListKeys(ListKeysRequest),
- GetKeyInfo(GetKeyInfoRequest),
- CreateKey(CreateKeyRequest),
- ImportKey(ImportKeyRequest),
- UpdateKey(UpdateKeyRequest),
- DeleteKey(DeleteKeyRequest),
+ ListKeys,
+ GetKeyInfo,
+ CreateKey,
+ ImportKey,
+ UpdateKey,
+ DeleteKey,
// Bucket operations
- ListBuckets(ListBucketsRequest),
- GetBucketInfo(GetBucketInfoRequest),
- CreateBucket(CreateBucketRequest),
- UpdateBucket(UpdateBucketRequest),
- DeleteBucket(DeleteBucketRequest),
+ ListBuckets,
+ GetBucketInfo,
+ CreateBucket,
+ UpdateBucket,
+ DeleteBucket,
// Operations on permissions for keys on buckets
- BucketAllowKey(BucketAllowKeyRequest),
- BucketDenyKey(BucketDenyKeyRequest),
+ BucketAllowKey,
+ BucketDenyKey,
// Operations on bucket aliases
- GlobalAliasBucket(GlobalAliasBucketRequest),
- GlobalUnaliasBucket(GlobalUnaliasBucketRequest),
- LocalAliasBucket(LocalAliasBucketRequest),
- LocalUnaliasBucket(LocalUnaliasBucketRequest),
-}
-
-#[derive(Serialize)]
-#[serde(untagged)]
-pub enum AdminApiResponse {
- // Cluster operations
- GetClusterStatus(GetClusterStatusResponse),
- GetClusterHealth(GetClusterHealthResponse),
- ConnectClusterNodes(ConnectClusterNodesResponse),
- GetClusterLayout(GetClusterLayoutResponse),
- UpdateClusterLayout(UpdateClusterLayoutResponse),
- ApplyClusterLayout(ApplyClusterLayoutResponse),
- RevertClusterLayout(RevertClusterLayoutResponse),
-
- // Access key operations
- ListKeys(ListKeysResponse),
- GetKeyInfo(GetKeyInfoResponse),
- CreateKey(CreateKeyResponse),
- ImportKey(ImportKeyResponse),
- UpdateKey(UpdateKeyResponse),
- DeleteKey(DeleteKeyResponse),
-
- // Bucket operations
- ListBuckets(ListBucketsResponse),
- GetBucketInfo(GetBucketInfoResponse),
- CreateBucket(CreateBucketResponse),
- UpdateBucket(UpdateBucketResponse),
- DeleteBucket(DeleteBucketResponse),
-
- // Operations on permissions for keys on buckets
- BucketAllowKey(BucketAllowKeyResponse),
- BucketDenyKey(BucketDenyKeyResponse),
-
- // Operations on bucket aliases
- GlobalAliasBucket(GlobalAliasBucketResponse),
- GlobalUnaliasBucket(GlobalUnaliasBucketResponse),
- LocalAliasBucket(LocalAliasBucketResponse),
- LocalUnaliasBucket(LocalUnaliasBucketResponse),
-}
-
-#[async_trait]
-impl EndpointHandler for AdminApiRequest {
- type Response = AdminApiResponse;
-
- async fn handle(self, garage: &Arc<Garage>) -> Result<AdminApiResponse, Error> {
- Ok(match self {
- Self::Options | Self::CheckDomain | Self::Health | Self::Metrics => unreachable!(),
- // Cluster operations
- Self::GetClusterStatus(req) => {
- AdminApiResponse::GetClusterStatus(req.handle(garage).await?)
- }
- Self::GetClusterHealth(req) => {
- AdminApiResponse::GetClusterHealth(req.handle(garage).await?)
- }
- Self::ConnectClusterNodes(req) => {
- AdminApiResponse::ConnectClusterNodes(req.handle(garage).await?)
- }
- Self::GetClusterLayout(req) => {
- AdminApiResponse::GetClusterLayout(req.handle(garage).await?)
- }
- Self::UpdateClusterLayout(req) => {
- AdminApiResponse::UpdateClusterLayout(req.handle(garage).await?)
- }
- Self::ApplyClusterLayout(req) => {
- AdminApiResponse::ApplyClusterLayout(req.handle(garage).await?)
- }
- Self::RevertClusterLayout(req) => {
- AdminApiResponse::RevertClusterLayout(req.handle(garage).await?)
- }
-
- // Access key operations
- Self::ListKeys(req) => AdminApiResponse::ListKeys(req.handle(garage).await?),
- Self::GetKeyInfo(req) => AdminApiResponse::GetKeyInfo(req.handle(garage).await?),
- Self::CreateKey(req) => AdminApiResponse::CreateKey(req.handle(garage).await?),
- Self::ImportKey(req) => AdminApiResponse::ImportKey(req.handle(garage).await?),
- Self::UpdateKey(req) => AdminApiResponse::UpdateKey(req.handle(garage).await?),
- Self::DeleteKey(req) => AdminApiResponse::DeleteKey(req.handle(garage).await?),
-
- // Bucket operations
- Self::ListBuckets(req) => AdminApiResponse::ListBuckets(req.handle(garage).await?),
- Self::GetBucketInfo(req) => AdminApiResponse::GetBucketInfo(req.handle(garage).await?),
- Self::CreateBucket(req) => AdminApiResponse::CreateBucket(req.handle(garage).await?),
- Self::UpdateBucket(req) => AdminApiResponse::UpdateBucket(req.handle(garage).await?),
- Self::DeleteBucket(req) => AdminApiResponse::DeleteBucket(req.handle(garage).await?),
-
- // Operations on permissions for keys on buckets
- Self::BucketAllowKey(req) => {
- AdminApiResponse::BucketAllowKey(req.handle(garage).await?)
- }
- Self::BucketDenyKey(req) => AdminApiResponse::BucketDenyKey(req.handle(garage).await?),
-
- // Operations on bucket aliases
- Self::GlobalAliasBucket(req) => {
- AdminApiResponse::GlobalAliasBucket(req.handle(garage).await?)
- }
- Self::GlobalUnaliasBucket(req) => {
- AdminApiResponse::GlobalUnaliasBucket(req.handle(garage).await?)
- }
- Self::LocalAliasBucket(req) => {
- AdminApiResponse::LocalAliasBucket(req.handle(garage).await?)
- }
- Self::LocalUnaliasBucket(req) => {
- AdminApiResponse::LocalUnaliasBucket(req.handle(garage).await?)
- }
- })
- }
-}
+ GlobalAliasBucket,
+ GlobalUnaliasBucket,
+ LocalAliasBucket,
+ LocalUnaliasBucket,
+];
// **********************************************
// Special endpoints
diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs
index b235dafc..e00f17c4 100644
--- a/src/api/admin/api_server.rs
+++ b/src/api/admin/api_server.rs
@@ -1,10 +1,10 @@
use std::borrow::Cow;
-use std::collections::HashMap;
use std::sync::Arc;
use argon2::password_hash::PasswordHash;
use async_trait::async_trait;
+use http::header::AUTHORIZATION;
use hyper::{body::Incoming as IncomingBody, Request, Response, StatusCode};
use tokio::sync::watch;
@@ -16,7 +16,6 @@ use opentelemetry_prometheus::PrometheusExporter;
use prometheus::{Encoder, TextEncoder};
use garage_model::garage::Garage;
-use garage_rpc::system::ClusterHealthStatus;
use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress;
@@ -26,6 +25,7 @@ use crate::admin::api::*;
use crate::admin::error::*;
use crate::admin::router_v0;
use crate::admin::router_v1;
+use crate::admin::Authorization;
use crate::admin::EndpointHandler;
use crate::helpers::*;
@@ -40,7 +40,7 @@ pub struct AdminApiServer {
}
enum Endpoint {
- Old(endpoint_v1::Endpoint),
+ Old(router_v1::Endpoint),
New(String),
}
@@ -112,7 +112,7 @@ impl ApiHandler for AdminApiServer {
fn parse_endpoint(&self, req: &Request<IncomingBody>) -> Result<Endpoint, Error> {
if req.uri().path().starts_with("/v0/") {
let endpoint_v0 = router_v0::Endpoint::from_request(req)?;
- let endpoint_v1 = router_v1::Endpoint::from_v0(endpoint_v0);
+ let endpoint_v1 = router_v1::Endpoint::from_v0(endpoint_v0)?;
Ok(Endpoint::Old(endpoint_v1))
} else if req.uri().path().starts_with("/v1/") {
let endpoint_v1 = router_v1::Endpoint::from_request(req)?;
@@ -127,6 +127,8 @@ impl ApiHandler for AdminApiServer {
req: Request<IncomingBody>,
endpoint: Endpoint,
) -> Result<Response<ResBody>, Error> {
+ let auth_header = req.headers().get(AUTHORIZATION).clone();
+
let request = match endpoint {
Endpoint::Old(endpoint_v1) => {
todo!() // TODO: convert from old semantics, if possible
@@ -147,7 +149,7 @@ impl ApiHandler for AdminApiServer {
};
if let Some(password_hash) = required_auth_hash {
- match req.headers().get("Authorization") {
+ match auth_header {
None => return Err(Error::forbidden("Authorization token must be provided")),
Some(authorization) => {
verify_bearer_token(&authorization, password_hash)?;
@@ -169,10 +171,10 @@ impl ApiHandler for AdminApiServer {
}
impl ApiEndpoint for Endpoint {
- fn name(&self) -> Cow<'_, str> {
+ fn name(&self) -> Cow<'static, str> {
match self {
- Self::Old(endpoint_v1) => Cow::owned(format!("v1:{}", endpoint_v1.name)),
- Self::New(path) => Cow::borrowed(&path),
+ Self::Old(endpoint_v1) => Cow::Owned(format!("v1:{}", endpoint_v1.name())),
+ Self::New(path) => Cow::Owned(path.clone()),
}
}
diff --git a/src/api/admin/macros.rs b/src/api/admin/macros.rs
new file mode 100644
index 00000000..a12dc40b
--- /dev/null
+++ b/src/api/admin/macros.rs
@@ -0,0 +1,58 @@
+macro_rules! admin_endpoints {
+ [
+ $(@special $special_endpoint:ident,)*
+ $($endpoint:ident,)*
+ ] => {
+ paste! {
+ pub enum AdminApiRequest {
+ $(
+ $special_endpoint( [<$special_endpoint Request>] ),
+ )*
+ $(
+ $endpoint( [<$endpoint Request>] ),
+ )*
+ }
+
+ #[derive(Serialize)]
+ #[serde(untagged)]
+ pub enum AdminApiResponse {
+ $(
+ $endpoint( [<$endpoint Response>] ),
+ )*
+ }
+
+ impl AdminApiRequest {
+ fn name(&self) -> &'static str {
+ match self {
+ $(
+ Self::$special_endpoint(_) => stringify!($special_endpoint),
+ )*
+ $(
+ Self::$endpoint(_) => stringify!($endpoint),
+ )*
+ }
+ }
+ }
+
+ #[async_trait]
+ impl EndpointHandler for AdminApiRequest {
+ type Response = AdminApiResponse;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<AdminApiResponse, Error> {
+ Ok(match self {
+ $(
+ AdminApiRequest::$special_endpoint(_) => panic!(
+ concat!(stringify!($special_endpoint), " needs to go through a special handler")
+ ),
+ )*
+ $(
+ AdminApiRequest::$endpoint(req) => AdminApiResponse::$endpoint(req.handle(garage).await?),
+ )*
+ })
+ }
+ }
+ }
+ };
+}
+
+pub(crate) use admin_endpoints;
diff --git a/src/api/admin/mod.rs b/src/api/admin/mod.rs
index f4c37298..86f5bcac 100644
--- a/src/api/admin/mod.rs
+++ b/src/api/admin/mod.rs
@@ -1,5 +1,6 @@
pub mod api_server;
mod error;
+mod macros;
pub mod api;
mod router_v0;
diff --git a/src/api/admin/router_v2.rs b/src/api/admin/router_v2.rs
index 9d203500..f9a976c4 100644
--- a/src/api/admin/router_v2.rs
+++ b/src/api/admin/router_v2.rs
@@ -15,7 +15,7 @@ impl AdminApiRequest {
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
/// possibly extracted from the Host header.
/// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets
- pub async fn from_request<T>(req: Request<IncomingBody>) -> Result<Self, Error> {
+ pub async fn from_request(req: Request<IncomingBody>) -> Result<Self, Error> {
let uri = req.uri().clone();
let path = uri.path();
let query = uri.query();
diff --git a/src/api/generic_server.rs b/src/api/generic_server.rs
index ce2ff7b7..5a9b29eb 100644
--- a/src/api/generic_server.rs
+++ b/src/api/generic_server.rs
@@ -38,7 +38,7 @@ use garage_util::socket_address::UnixOrTCPSocketAddress;
use crate::helpers::{BoxBody, ErrorBody};
pub(crate) trait ApiEndpoint: Send + Sync + 'static {
- fn name(&self) -> Cow<'_, str>;
+ fn name(&self) -> Cow<'static, str>;
fn add_span_attributes(&self, span: SpanRef<'_>);
}
diff --git a/src/api/k2v/api_server.rs b/src/api/k2v/api_server.rs
index 35931914..863452e6 100644
--- a/src/api/k2v/api_server.rs
+++ b/src/api/k2v/api_server.rs
@@ -182,8 +182,8 @@ impl ApiHandler for K2VApiServer {
}
impl ApiEndpoint for K2VApiEndpoint {
- fn name(&self) -> Cow<'_, str> {
- Cow::borrowed(self.endpoint.name())
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed(self.endpoint.name())
}
fn add_span_attributes(&self, span: SpanRef<'_>) {
diff --git a/src/api/s3/api_server.rs b/src/api/s3/api_server.rs
index 3820ad8f..2b638b15 100644
--- a/src/api/s3/api_server.rs
+++ b/src/api/s3/api_server.rs
@@ -357,8 +357,8 @@ impl ApiHandler for S3ApiServer {
}
impl ApiEndpoint for S3ApiEndpoint {
- fn name(&self) -> Cow<'_, str> {
- Cow::borrowed(self.endpoint.name())
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed(self.endpoint.name())
}
fn add_span_attributes(&self, span: SpanRef<'_>) {