aboutsummaryrefslogtreecommitdiff
path: root/src/api/admin/router_v1.rs
diff options
context:
space:
mode:
authorAlex <alex@adnab.me>2023-10-10 13:28:28 +0000
committerAlex <alex@adnab.me>2023-10-10 13:28:28 +0000
commite6df7089a19d06696ac2178a4dae7ac9f27fd55e (patch)
treee1f7120d582f67fd104dc8e746d883befffb4daa /src/api/admin/router_v1.rs
parente75fe2157d0973eb4320bbc87be5e54a1cb91a63 (diff)
parent952c9570c494468643353ee1ae9052b510353665 (diff)
downloadgarage-e6df7089a19d06696ac2178a4dae7ac9f27fd55e.tar.gz
garage-e6df7089a19d06696ac2178a4dae7ac9f27fd55e.zip
Merge pull request 'Garage v0.9' (#473) from next into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/473
Diffstat (limited to 'src/api/admin/router_v1.rs')
-rw-r--r--src/api/admin/router_v1.rs235
1 files changed, 235 insertions, 0 deletions
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
+ ]
+}