From 795b4a41b72dcc786849e5f6bf69a24eea114ca3 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 12 Mar 2025 10:52:58 +0100 Subject: [PATCH 1/2] admin api: add special endpoints to openapi spec --- doc/api/garage-admin-v2.json | 68 ++++++++++++++++++++++++++++++++++++ src/api/admin/openapi.rs | 57 ++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/doc/api/garage-admin-v2.json b/doc/api/garage-admin-v2.json index 91d92e11..9379fee5 100644 --- a/doc/api/garage-admin-v2.json +++ b/doc/api/garage-admin-v2.json @@ -21,6 +21,74 @@ } ], "paths": { + "/check": { + "get": { + "tags": [ + "Special endpoints" + ], + "description": "\nStatic website domain name check. Checks whether a bucket is configured to serve\na static website for the requested domain. This is used by reverse proxies such\nas Caddy or Tricot, to avoid requesting TLS certificates for domain names that\ndo not correspond to an actual website.\n ", + "operationId": "CheckDomain", + "parameters": [ + { + "name": "domain", + "in": "path", + "description": "The domain name to check for", + "required": true + } + ], + "responses": { + "200": { + "description": "The domain name redirects to a static website bucket" + }, + "400": { + "description": "No static website bucket exists for this domain" + } + }, + "security": [ + {} + ] + } + }, + "/health": { + "get": { + "tags": [ + "Special endpoints" + ], + "description": "\nCheck cluster health. The status code returned by this function indicates\nwhether this Garage daemon can answer API requests.\nGarage will return `200 OK` even if some storage nodes are disconnected,\nas long as it is able to have a quorum of nodes for read and write operations.\n ", + "operationId": "Health", + "responses": { + "200": { + "description": "Garage is able to answer requests" + }, + "503": { + "description": "This Garage daemon is not able to handle requests" + } + }, + "security": [ + {} + ] + } + }, + "/metrics": { + "get": { + "tags": [ + "Special endpoints" + ], + "description": "Prometheus metrics endpoint", + "operationId": "Metrics", + "responses": { + "200": { + "description": "Garage daemon metrics exported in Prometheus format" + } + }, + "security": [ + {}, + { + "bearerAuth": [] + } + ] + } + }, "/v2/AddBucketAlias": { "post": { "tags": [ diff --git a/src/api/admin/openapi.rs b/src/api/admin/openapi.rs index 24319817..77d8dce8 100644 --- a/src/api/admin/openapi.rs +++ b/src/api/admin/openapi.rs @@ -5,6 +5,59 @@ use utoipa::{Modify, OpenApi}; use crate::api::*; +// ********************************************** +// Special endpoints +// ********************************************** + +#[utoipa::path(get, + path = "/metrics", + tag = "Special endpoints", + description = "Prometheus metrics endpoint", + security((), ("bearerAuth" = [])), + responses( + (status = 200, description = "Garage daemon metrics exported in Prometheus format"), + ), +)] +fn Metrics() -> () {} + +#[utoipa::path(get, + path = "/health", + tag = "Special endpoints", + description = " +Check cluster health. The status code returned by this function indicates +whether this Garage daemon can answer API requests. +Garage will return `200 OK` even if some storage nodes are disconnected, +as long as it is able to have a quorum of nodes for read and write operations. + ", + security(()), + responses( + (status = 200, description = "Garage is able to answer requests"), + (status = 503, description = "This Garage daemon is not able to handle requests") + ), +)] +fn Health() -> () {} + +#[utoipa::path(get, + path = "/check", + tag = "Special endpoints", + description = " +Static website domain name check. Checks whether a bucket is configured to serve +a static website for the requested domain. This is used by reverse proxies such +as Caddy or Tricot, to avoid requesting TLS certificates for domain names that +do not correspond to an actual website. + ", + params( + ("domain", description = "The domain name to check for"), + ), + security(()), + responses( + (status = 200, description = "The domain name redirects to a static website bucket"), + (status = 400, description = "No static website bucket exists for this domain") + ), +)] +fn CheckDomain() -> () {} + + // ********************************************** // Cluster operations // ********************************************** @@ -794,6 +847,10 @@ impl Modify for SecurityAddon { modifiers(&SecurityAddon), security(("bearerAuth" = [])), paths( + // Special ops + Metrics, + Health, + CheckDomain, // Cluster operations GetClusterHealth, GetClusterStatus, From 0b12debf6c359070802816f6ca5264dfd02e231d Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 12 Mar 2025 11:07:12 +0100 Subject: [PATCH 2/2] admin api: generate params from struct --- doc/api/garage-admin-v2.json | 61 +++++++++++++++++++++++++++++++----- src/api/admin/api.rs | 20 +++++++++--- src/api/admin/openapi.rs | 18 ++--------- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/doc/api/garage-admin-v2.json b/doc/api/garage-admin-v2.json index 9379fee5..fbb1f6c5 100644 --- a/doc/api/garage-admin-v2.json +++ b/doc/api/garage-admin-v2.json @@ -554,13 +554,25 @@ "name": "id", "in": "path", "description": "Admin API token ID", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } }, { "name": "search", "in": "path", "description": "Partial token ID or name to search for", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } } ], "responses": { @@ -634,19 +646,37 @@ "name": "id", "in": "path", "description": "Exact bucket ID to look up", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } }, { "name": "globalAlias", "in": "path", "description": "Global alias of bucket to look up", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } }, { "name": "search", "in": "path", "description": "Partial ID or alias to search for", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } } ], "responses": { @@ -795,19 +825,34 @@ "name": "id", "in": "path", "description": "Access key ID", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } }, { "name": "search", "in": "path", "description": "Partial key ID or name to search for", - "required": true + "required": true, + "schema": { + "type": [ + "string", + "null" + ] + } }, { "name": "showSecretKey", "in": "path", "description": "Whether to return the secret access key", - "required": true + "required": true, + "schema": { + "type": "boolean" + } } ], "responses": { diff --git a/src/api/admin/api.rs b/src/api/admin/api.rs index fde304f4..3694fd67 100644 --- a/src/api/admin/api.rs +++ b/src/api/admin/api.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use paste::paste; use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; +use utoipa::{IntoParams, ToSchema}; use garage_rpc::*; @@ -303,9 +303,12 @@ pub struct ListAdminTokensResponse(pub Vec); // ---- GetAdminTokenInfo ---- -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, IntoParams)] +#[serde(rename_all = "camelCase")] pub struct GetAdminTokenInfoRequest { + /// Admin API token ID pub id: Option, + /// Partial token ID or name to search for pub search: Option, } @@ -634,10 +637,15 @@ pub struct ListKeysResponseItem { // ---- GetKeyInfo ---- -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, IntoParams)] +#[serde(rename_all = "camelCase")] pub struct GetKeyInfoRequest { + /// Access key ID pub id: Option, + /// Partial key ID or name to search for pub search: Option, + /// Whether to return the secret access key + #[serde(default)] pub show_secret_key: bool, } @@ -761,10 +769,14 @@ pub struct BucketLocalAlias { // ---- GetBucketInfo ---- -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, IntoParams)] +#[serde(rename_all = "camelCase")] pub struct GetBucketInfoRequest { + /// Exact bucket ID to look up pub id: Option, + /// Global alias of bucket to look up pub global_alias: Option, + /// Partial ID or alias to search for pub search: Option, } diff --git a/src/api/admin/openapi.rs b/src/api/admin/openapi.rs index 77d8dce8..b7ffdcf1 100644 --- a/src/api/admin/openapi.rs +++ b/src/api/admin/openapi.rs @@ -57,7 +57,6 @@ do not correspond to an actual website. )] fn CheckDomain() -> () {} - // ********************************************** // Cluster operations // ********************************************** @@ -141,10 +140,7 @@ fn ListAdminTokens() -> () {} Return information about a specific admin API token. You can search by specifying the exact token identifier (`id`) or by specifying a pattern (`search`). ", - params( - ("id", description = "Admin API token ID"), - ("search", description = "Partial token ID or name to search for"), - ), + params(GetAdminTokenInfoRequest), responses( (status = 200, description = "Information about the admin token", body = GetAdminTokenInfoResponse), (status = 500, description = "Internal server error") @@ -337,11 +333,7 @@ You can search by specifying the exact key identifier (`id`) or by specifying a For confidentiality reasons, the secret key is not returned by default: you must pass the `showSecretKey` query parameter to get it. ", - params( - ("id", description = "Access key ID"), - ("search", description = "Partial key ID or name to search for"), - ("showSecretKey", description = "Whether to return the secret access key"), - ), + params(GetKeyInfoRequest), responses( (status = 200, description = "Information about the access key", body = GetKeyInfoResponse), (status = 500, description = "Internal server error") @@ -434,11 +426,7 @@ It includes its aliases, its web configuration, keys that have some permissions on it, some statistics (number of objects, size), number of dangling multipart uploads, and its quotas (if any). ", - params( - ("id", description = "Exact bucket ID to look up"), - ("globalAlias", description = "Global alias of bucket to look up"), - ("search", description = "Partial ID or alias to search for"), - ), + params(GetBucketInfoRequest), responses( (status = 200, description = "Returns exhaustive information about the bucket", body = GetBucketInfoResponse), (status = 500, description = "Internal server error")