aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2023-10-05 16:56:13 +0200
committerAlex Auvolat <alex@adnab.me>2023-10-05 16:56:13 +0200
commit0c431b0c035f4de8ea9d1d9bd0b419bfc74ceabf (patch)
tree23605595e8faf3712f2dad303aa2cb49e61a0f4d
parent1c13135f253007dfd5c56b2ddf3412c9d66458ec (diff)
downloadgarage-0c431b0c035f4de8ea9d1d9bd0b419bfc74ceabf.tar.gz
garage-0c431b0c035f4de8ea9d1d9bd0b419bfc74ceabf.zip
admin api: increased compatibility for v0/ endpointsv0.9.0-rc2
-rw-r--r--src/api/admin/api_server.rs10
-rw-r--r--src/api/admin/mod.rs3
-rw-r--r--src/api/admin/router.rs160
-rw-r--r--src/api/admin/router_v0.rs143
-rw-r--r--src/api/admin/router_v1.rs235
5 files changed, 388 insertions, 163 deletions
diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs
index 53503220..4779f924 100644
--- a/src/api/admin/api_server.rs
+++ b/src/api/admin/api_server.rs
@@ -25,7 +25,8 @@ use crate::admin::bucket::*;
use crate::admin::cluster::*;
use crate::admin::error::*;
use crate::admin::key::*;
-use crate::admin::router::{Authorization, Endpoint};
+use crate::admin::router_v0;
+use crate::admin::router_v1::{Authorization, Endpoint};
use crate::helpers::host_to_bucket;
pub struct AdminApiServer {
@@ -229,7 +230,12 @@ impl ApiHandler for AdminApiServer {
type Error = Error;
fn parse_endpoint(&self, req: &Request<Body>) -> Result<Endpoint, Error> {
- Endpoint::from_request(req)
+ if req.uri().path().starts_with("/v0/") {
+ let endpoint_v0 = router_v0::Endpoint::from_request(req)?;
+ Endpoint::from_v0(endpoint_v0)
+ } else {
+ Endpoint::from_request(req)
+ }
}
async fn handle(
diff --git a/src/api/admin/mod.rs b/src/api/admin/mod.rs
index c4857c10..43a8c59c 100644
--- a/src/api/admin/mod.rs
+++ b/src/api/admin/mod.rs
@@ -1,6 +1,7 @@
pub mod api_server;
mod error;
-mod router;
+mod router_v0;
+mod router_v1;
mod bucket;
mod cluster;
diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs
deleted file mode 100644
index 691d1932..00000000
--- a/src/api/admin/router.rs
+++ /dev/null
@@ -1,160 +0,0 @@
-use std::borrow::Cow;
-
-use hyper::{Method, Request};
-
-use crate::admin::error::*;
-use crate::router_macros::*;
-
-pub enum Authorization {
- None,
- MetricsToken,
- AdminToken,
-}
-
-router_match! {@func
-
-/// List of all Admin API endpoints.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Endpoint {
- Options,
- CheckDomain,
- Health,
- Metrics,
- GetClusterStatus,
- GetClusterHealth,
- ConnectClusterNodes,
- // Layout
- GetClusterLayout,
- UpdateClusterLayout,
- ApplyClusterLayout,
- RevertClusterLayout,
- // Keys
- ListKeys,
- CreateKey,
- ImportKey,
- GetKeyInfo {
- id: Option<String>,
- search: Option<String>,
- show_secret_key: Option<String>,
- },
- DeleteKey {
- id: String,
- },
- UpdateKey {
- id: String,
- },
- // Buckets
- ListBuckets,
- CreateBucket,
- GetBucketInfo {
- id: Option<String>,
- global_alias: Option<String>,
- },
- DeleteBucket {
- id: String,
- },
- UpdateBucket {
- id: String,
- },
- // Bucket-Key Permissions
- BucketAllowKey,
- BucketDenyKey,
- // Bucket aliases
- GlobalAliasBucket {
- id: String,
- alias: String,
- },
- GlobalUnaliasBucket {
- id: String,
- alias: String,
- },
- LocalAliasBucket {
- id: String,
- access_key_id: String,
- alias: String,
- },
- LocalUnaliasBucket {
- id: String,
- access_key_id: String,
- alias: String,
- },
-}}
-
-impl Endpoint {
- /// 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 fn from_request<T>(req: &Request<T>) -> Result<Self, Error> {
- let uri = req.uri();
- let path = uri.path();
- let query = uri.query();
-
- let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
-
- let res = router_match!(@gen_path_parser (req.method(), path, query) [
- OPTIONS _ => Options,
- GET "/check" => CheckDomain,
- GET "/health" => Health,
- GET "/metrics" => Metrics,
- GET "/v1/status" => GetClusterStatus,
- GET "/v1/health" => GetClusterHealth,
- POST ("/v0/connect" | "/v1/connect") => ConnectClusterNodes,
- // Layout endpoints
- GET "/v1/layout" => GetClusterLayout,
- POST "/v1/layout" => UpdateClusterLayout,
- POST "/v1/layout/apply" => ApplyClusterLayout,
- POST ("/v0/layout/revert" | "/v1/layout/revert") => RevertClusterLayout,
- // API key endpoints
- GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
- GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
- POST "/v1/key" if id => UpdateKey (query::id),
- POST "/v1/key" => CreateKey,
- POST "/v1/key/import" => ImportKey,
- DELETE ("/v0/key" | "/v1/key") if id => DeleteKey (query::id),
- GET "/v1/key" => ListKeys,
- // Bucket endpoints
- GET ("/v0/bucket" | "/v1/bucket") if id => GetBucketInfo (query_opt::id, query_opt::global_alias),
- GET ("/v0/bucket" | "/v1/bucket") if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias),
- GET ("/v0/bucket" | "/v1/bucket") => ListBuckets,
- POST ("/v0/bucket" | "/v1/bucket") => CreateBucket,
- DELETE ("/v0/bucket" | "/v1/bucket") if id => DeleteBucket (query::id),
- PUT ("/v0/bucket" | "/v1/bucket") if id => UpdateBucket (query::id),
- // Bucket-key permissions
- POST ("/v0/bucket/allow" | "/v1/bucket/allow") => BucketAllowKey,
- POST ("/v0/bucket/deny" | "/v1/bucket/deny") => BucketDenyKey,
- // Bucket aliases
- PUT ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalAliasBucket (query::id, query::alias),
- DELETE ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalUnaliasBucket (query::id, query::alias),
- PUT ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalAliasBucket (query::id, query::access_key_id, query::alias),
- DELETE ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
- ]);
-
- if let Some(message) = query.nonempty_message() {
- debug!("Unused query parameter: {}", message)
- }
-
- Ok(res)
- }
- /// Get the kind of authorization which is required to perform the operation.
- pub fn authorization_type(&self) -> Authorization {
- match self {
- Self::Health => Authorization::None,
- Self::CheckDomain => Authorization::None,
- Self::Metrics => Authorization::MetricsToken,
- _ => Authorization::AdminToken,
- }
- }
-}
-
-generateQueryParameters! {
- keywords: [],
- fields: [
- "format" => format,
- "id" => id,
- "search" => search,
- "globalAlias" => global_alias,
- "alias" => alias,
- "accessKeyId" => access_key_id,
- "showSecretKey" => show_secret_key
- ]
-}
diff --git a/src/api/admin/router_v0.rs b/src/api/admin/router_v0.rs
new file mode 100644
index 00000000..68676445
--- /dev/null
+++ b/src/api/admin/router_v0.rs
@@ -0,0 +1,143 @@
+use std::borrow::Cow;
+
+use hyper::{Method, Request};
+
+use crate::admin::error::*;
+use crate::router_macros::*;
+
+router_match! {@func
+
+/// List of all Admin API endpoints.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Endpoint {
+ Options,
+ CheckDomain,
+ Health,
+ Metrics,
+ GetClusterStatus,
+ GetClusterHealth,
+ ConnectClusterNodes,
+ // Layout
+ GetClusterLayout,
+ UpdateClusterLayout,
+ ApplyClusterLayout,
+ RevertClusterLayout,
+ // Keys
+ ListKeys,
+ CreateKey,
+ ImportKey,
+ GetKeyInfo {
+ id: Option<String>,
+ search: Option<String>,
+ },
+ DeleteKey {
+ id: String,
+ },
+ UpdateKey {
+ id: String,
+ },
+ // Buckets
+ ListBuckets,
+ CreateBucket,
+ GetBucketInfo {
+ id: Option<String>,
+ global_alias: Option<String>,
+ },
+ DeleteBucket {
+ id: String,
+ },
+ UpdateBucket {
+ id: String,
+ },
+ // Bucket-Key Permissions
+ BucketAllowKey,
+ BucketDenyKey,
+ // Bucket aliases
+ GlobalAliasBucket {
+ id: String,
+ alias: String,
+ },
+ GlobalUnaliasBucket {
+ id: String,
+ alias: String,
+ },
+ LocalAliasBucket {
+ id: String,
+ access_key_id: String,
+ alias: String,
+ },
+ LocalUnaliasBucket {
+ id: String,
+ access_key_id: String,
+ alias: String,
+ },
+}}
+
+impl Endpoint {
+ /// 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 fn from_request<T>(req: &Request<T>) -> Result<Self, Error> {
+ let uri = req.uri();
+ let path = uri.path();
+ let query = uri.query();
+
+ let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
+
+ let res = router_match!(@gen_path_parser (req.method(), path, query) [
+ OPTIONS _ => Options,
+ GET "/check" => CheckDomain,
+ GET "/health" => Health,
+ GET "/metrics" => Metrics,
+ GET "/v0/status" => GetClusterStatus,
+ GET "/v0/health" => GetClusterHealth,
+ POST "/v0/connect" => ConnectClusterNodes,
+ // Layout endpoints
+ GET "/v0/layout" => GetClusterLayout,
+ POST "/v0/layout" => UpdateClusterLayout,
+ POST "/v0/layout/apply" => ApplyClusterLayout,
+ POST "/v0/layout/revert" => RevertClusterLayout,
+ // API key endpoints
+ GET "/v0/key" if id => GetKeyInfo (query_opt::id, query_opt::search),
+ GET "/v0/key" if search => GetKeyInfo (query_opt::id, query_opt::search),
+ POST "/v0/key" if id => UpdateKey (query::id),
+ POST "/v0/key" => CreateKey,
+ POST "/v0/key/import" => ImportKey,
+ DELETE "/v0/key" if id => DeleteKey (query::id),
+ GET "/v0/key" => ListKeys,
+ // Bucket endpoints
+ GET "/v0/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias),
+ GET "/v0/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias),
+ GET "/v0/bucket" => ListBuckets,
+ POST "/v0/bucket" => CreateBucket,
+ DELETE "/v0/bucket" if id => DeleteBucket (query::id),
+ PUT "/v0/bucket" if id => UpdateBucket (query::id),
+ // Bucket-key permissions
+ POST "/v0/bucket/allow" => BucketAllowKey,
+ POST "/v0/bucket/deny" => BucketDenyKey,
+ // Bucket aliases
+ PUT "/v0/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias),
+ DELETE "/v0/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias),
+ PUT "/v0/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias),
+ DELETE "/v0/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
+ ]);
+
+ if let Some(message) = query.nonempty_message() {
+ debug!("Unused query parameter: {}", message)
+ }
+
+ Ok(res)
+ }
+}
+
+generateQueryParameters! {
+ keywords: [],
+ fields: [
+ "format" => format,
+ "id" => id,
+ "search" => search,
+ "globalAlias" => global_alias,
+ "alias" => alias,
+ "accessKeyId" => access_key_id
+ ]
+}
diff --git a/src/api/admin/router_v1.rs b/src/api/admin/router_v1.rs
new file mode 100644
index 00000000..cc5ff2ec
--- /dev/null
+++ b/src/api/admin/router_v1.rs
@@ -0,0 +1,235 @@
+use std::borrow::Cow;
+
+use hyper::{Method, Request};
+
+use crate::admin::error::*;
+use crate::admin::router_v0;
+use crate::router_macros::*;
+
+pub enum Authorization {
+ None,
+ MetricsToken,
+ AdminToken,
+}
+
+router_match! {@func
+
+/// List of all Admin API endpoints.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Endpoint {
+ Options,
+ CheckDomain,
+ Health,
+ Metrics,
+ GetClusterStatus,
+ GetClusterHealth,
+ ConnectClusterNodes,
+ // Layout
+ GetClusterLayout,
+ UpdateClusterLayout,
+ ApplyClusterLayout,
+ RevertClusterLayout,
+ // Keys
+ ListKeys,
+ CreateKey,
+ ImportKey,
+ GetKeyInfo {
+ id: Option<String>,
+ search: Option<String>,
+ show_secret_key: Option<String>,
+ },
+ DeleteKey {
+ id: String,
+ },
+ UpdateKey {
+ id: String,
+ },
+ // Buckets
+ ListBuckets,
+ CreateBucket,
+ GetBucketInfo {
+ id: Option<String>,
+ global_alias: Option<String>,
+ },
+ DeleteBucket {
+ id: String,
+ },
+ UpdateBucket {
+ id: String,
+ },
+ // Bucket-Key Permissions
+ BucketAllowKey,
+ BucketDenyKey,
+ // Bucket aliases
+ GlobalAliasBucket {
+ id: String,
+ alias: String,
+ },
+ GlobalUnaliasBucket {
+ id: String,
+ alias: String,
+ },
+ LocalAliasBucket {
+ id: String,
+ access_key_id: String,
+ alias: String,
+ },
+ LocalUnaliasBucket {
+ id: String,
+ access_key_id: String,
+ alias: String,
+ },
+}}
+
+impl Endpoint {
+ /// 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 fn from_request<T>(req: &Request<T>) -> Result<Self, Error> {
+ let uri = req.uri();
+ let path = uri.path();
+ let query = uri.query();
+
+ let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
+
+ let res = router_match!(@gen_path_parser (req.method(), path, query) [
+ OPTIONS _ => Options,
+ GET "/check" => CheckDomain,
+ GET "/health" => Health,
+ GET "/metrics" => Metrics,
+ GET "/v1/status" => GetClusterStatus,
+ GET "/v1/health" => GetClusterHealth,
+ POST "/v1/connect" => ConnectClusterNodes,
+ // Layout endpoints
+ GET "/v1/layout" => GetClusterLayout,
+ POST "/v1/layout" => UpdateClusterLayout,
+ POST "/v1/layout/apply" => ApplyClusterLayout,
+ POST "/v1/layout/revert" => RevertClusterLayout,
+ // API key endpoints
+ GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
+ GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
+ POST "/v1/key" if id => UpdateKey (query::id),
+ POST "/v1/key" => CreateKey,
+ POST "/v1/key/import" => ImportKey,
+ DELETE "/v1/key" if id => DeleteKey (query::id),
+ GET "/v1/key" => ListKeys,
+ // Bucket endpoints
+ GET "/v1/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias),
+ GET "/v1/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias),
+ GET "/v1/bucket" => ListBuckets,
+ POST "/v1/bucket" => CreateBucket,
+ DELETE "/v1/bucket" if id => DeleteBucket (query::id),
+ PUT "/v1/bucket" if id => UpdateBucket (query::id),
+ // Bucket-key permissions
+ POST "/v1/bucket/allow" => BucketAllowKey,
+ POST "/v1/bucket/deny" => BucketDenyKey,
+ // Bucket aliases
+ PUT "/v1/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias),
+ DELETE "/v1/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias),
+ PUT "/v1/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias),
+ DELETE "/v1/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
+ ]);
+
+ if let Some(message) = query.nonempty_message() {
+ debug!("Unused query parameter: {}", message)
+ }
+
+ Ok(res)
+ }
+ /// Some endpoints work exactly the same in their v1/ version as they did in their v0/ version.
+ /// For these endpoints, we can convert a v0/ call to its equivalent as if it was made using
+ /// its v1/ URL.
+ pub fn from_v0(v0_endpoint: router_v0::Endpoint) -> Result<Self, Error> {
+ match v0_endpoint {
+ // Cluster endpoints
+ router_v0::Endpoint::ConnectClusterNodes => Ok(Self::ConnectClusterNodes),
+ // - GetClusterStatus: response format changed
+ // - GetClusterHealth: response format changed
+
+ // Layout endpoints
+ router_v0::Endpoint::RevertClusterLayout => Ok(Self::RevertClusterLayout),
+ // - GetClusterLayout: response format changed
+ // - UpdateClusterLayout: query format changed
+ // - ApplyCusterLayout: response format changed
+
+ // Key endpoints
+ router_v0::Endpoint::ListKeys => Ok(Self::ListKeys),
+ router_v0::Endpoint::CreateKey => Ok(Self::CreateKey),
+ router_v0::Endpoint::GetKeyInfo { id, search } => Ok(Self::GetKeyInfo {
+ id,
+ search,
+ show_secret_key: Some("true".into()),
+ }),
+ router_v0::Endpoint::DeleteKey { id } => Ok(Self::DeleteKey { id }),
+ // - UpdateKey: response format changed (secret key no longer returned)
+
+ // Bucket endpoints
+ router_v0::Endpoint::GetBucketInfo { id, global_alias } => {
+ Ok(Self::GetBucketInfo { id, global_alias })
+ }
+ router_v0::Endpoint::ListBuckets => Ok(Self::ListBuckets),
+ router_v0::Endpoint::CreateBucket => Ok(Self::CreateBucket),
+ router_v0::Endpoint::DeleteBucket { id } => Ok(Self::DeleteBucket { id }),
+ router_v0::Endpoint::UpdateBucket { id } => Ok(Self::UpdateBucket { id }),
+
+ // Bucket-key permissions
+ router_v0::Endpoint::BucketAllowKey => Ok(Self::BucketAllowKey),
+ router_v0::Endpoint::BucketDenyKey => Ok(Self::BucketDenyKey),
+
+ // Bucket alias endpoints
+ router_v0::Endpoint::GlobalAliasBucket { id, alias } => {
+ Ok(Self::GlobalAliasBucket { id, alias })
+ }
+ router_v0::Endpoint::GlobalUnaliasBucket { id, alias } => {
+ Ok(Self::GlobalUnaliasBucket { id, alias })
+ }
+ router_v0::Endpoint::LocalAliasBucket {
+ id,
+ access_key_id,
+ alias,
+ } => Ok(Self::LocalAliasBucket {
+ id,
+ access_key_id,
+ alias,
+ }),
+ router_v0::Endpoint::LocalUnaliasBucket {
+ id,
+ access_key_id,
+ alias,
+ } => Ok(Self::LocalUnaliasBucket {
+ id,
+ access_key_id,
+ alias,
+ }),
+
+ // For endpoints that have different body content syntax, issue
+ // deprecation warning
+ _ => Err(Error::bad_request(format!(
+ "v0/ endpoint is no longer supported: {}",
+ v0_endpoint.name()
+ ))),
+ }
+ }
+ /// Get the kind of authorization which is required to perform the operation.
+ pub fn authorization_type(&self) -> Authorization {
+ match self {
+ Self::Health => Authorization::None,
+ Self::CheckDomain => Authorization::None,
+ Self::Metrics => Authorization::MetricsToken,
+ _ => Authorization::AdminToken,
+ }
+ }
+}
+
+generateQueryParameters! {
+ keywords: [],
+ fields: [
+ "format" => format,
+ "id" => id,
+ "search" => search,
+ "globalAlias" => global_alias,
+ "alias" => alias,
+ "accessKeyId" => access_key_id,
+ "showSecretKey" => show_secret_key
+ ]
+}