admin api: refactor using macro
This commit is contained in:
parent
c99bfe69ea
commit
af1a530834
8 changed files with 113 additions and 150 deletions
|
@ -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
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
58
src/api/admin/macros.rs
Normal file
58
src/api/admin/macros.rs
Normal file
|
@ -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;
|
|
@ -1,5 +1,6 @@
|
|||
pub mod api_server;
|
||||
mod error;
|
||||
mod macros;
|
||||
|
||||
pub mod api;
|
||||
mod router_v0;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<'_>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<'_>) {
|
||||
|
|
|
@ -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<'_>) {
|
||||
|
|
Loading…
Add table
Reference in a new issue