admin api: update semantics of some endpoints, and update doc

This commit is contained in:
Alex 2025-01-28 16:18:48 +01:00
parent 0737cb4b22
commit 3d20bfda00
6 changed files with 122 additions and 58 deletions

View file

@ -13,8 +13,9 @@ We will bump the version numbers prefixed to each API endpoint each time the syn
or semantics change, meaning that code that relies on these endpoints will break
when changes are introduced.
The Garage administration API was introduced in version 0.7.2, this document
does not apply to older versions of Garage.
The Garage administration API was introduced in version 0.7.2, and was
changed several times.
This document applies only to the Garage v2 API (starting with Garage v2.0.0).
## Access control
@ -52,11 +53,18 @@ Returns an HTTP status 200 if the node is ready to answer user's requests,
and an HTTP status 503 (Service Unavailable) if there are some partitions
for which a quorum of nodes is not available.
A simple textual message is also returned in a body with content-type `text/plain`.
See `/v1/health` for an API that also returns JSON output.
See `/v2/health` for an API that also returns JSON output.
### Other special endpoints
#### CheckDomain `GET /check?domain=<domain>`
Checks whether this Garage cluster serves a website for domain `<domain>`.
Returns HTTP 200 Ok if yes, or HTTP 4xx if no website is available for this domain.
### Cluster operations
#### GetClusterStatus `GET /v1/status`
#### GetClusterStatus `GET /v2/GetClusterStatus`
Returns the cluster's current status in JSON, including:
@ -70,7 +78,7 @@ Example response body:
```json
{
"node": "b10c110e4e854e5aa3f4637681befac755154b20059ec163254ddbfae86b09df",
"garageVersion": "v1.0.1",
"garageVersion": "v2.0.0",
"garageFeatures": [
"k2v",
"lmdb",
@ -169,7 +177,7 @@ Example response body:
}
```
#### GetClusterHealth `GET /v1/health`
#### GetClusterHealth `GET /v2/GetClusterHealth`
Returns the cluster's current health in JSON format, with the following variables:
@ -202,7 +210,7 @@ Example response body:
}
```
#### ConnectClusterNodes `POST /v1/connect`
#### ConnectClusterNodes `POST /v2/ConnectClusterNodes`
Instructs this Garage node to connect to other Garage nodes at specified addresses.
@ -232,7 +240,7 @@ Example response:
]
```
#### GetClusterLayout `GET /v1/layout`
#### GetClusterLayout `GET /v2/GetClusterLayout`
Returns the cluster's current layout in JSON, including:
@ -293,7 +301,7 @@ Example response body:
}
```
#### UpdateClusterLayout `POST /v1/layout`
#### UpdateClusterLayout `POST /v2/UpdateClusterLayout`
Send modifications to the cluster layout. These modifications will
be included in the staged role changes, visible in subsequent calls
@ -330,7 +338,7 @@ This returns the new cluster layout with the proposed staged changes,
as returned by GetClusterLayout.
#### ApplyClusterLayout `POST /v1/layout/apply`
#### ApplyClusterLayout `POST /v2/ApplyClusterLayout`
Applies to the cluster the layout changes currently registered as
staged layout changes.
@ -350,7 +358,7 @@ existing layout in the cluster.
This returns the message describing all the calculations done to compute the new
layout, as well as the description of the layout as returned by GetClusterLayout.
#### RevertClusterLayout `POST /v1/layout/revert`
#### RevertClusterLayout `POST /v2/RevertClusterLayout`
Clears all of the staged layout changes.
@ -374,7 +382,7 @@ as returned by GetClusterLayout.
### Access key operations
#### ListKeys `GET /v1/key`
#### ListKeys `GET /v2/ListKeys`
Returns all API access keys in the cluster.
@ -393,8 +401,8 @@ Example response:
]
```
#### GetKeyInfo `GET /v1/key?id=<acces key id>`
#### GetKeyInfo `GET /v1/key?search=<pattern>`
#### GetKeyInfo `GET /v2/GetKeyInfo?id=<acces key id>`
#### GetKeyInfo `GET /v2/GetKeyInfo?search=<pattern>`
Returns information about the requested API access key.
@ -468,7 +476,7 @@ Example response:
}
```
#### CreateKey `POST /v1/key`
#### CreateKey `POST /v2/CreateKey`
Creates a new API access key.
@ -483,7 +491,7 @@ Request body format:
This returns the key info, including the created secret key,
in the same format as the result of GetKeyInfo.
#### ImportKey `POST /v1/key/import`
#### ImportKey `POST /v2/ImportKey`
Imports an existing API key.
This will check that the imported key is in the valid format, i.e.
@ -501,7 +509,7 @@ Request body format:
This returns the key info in the same format as the result of GetKeyInfo.
#### UpdateKey `POST /v1/key?id=<acces key id>`
#### UpdateKey `POST /v2/UpdateKey?id=<acces key id>`
Updates information about the specified API access key.
@ -523,14 +531,14 @@ The possible flags in `allow` and `deny` are: `createBucket`.
This returns the key info in the same format as the result of GetKeyInfo.
#### DeleteKey `DELETE /v1/key?id=<acces key id>`
#### DeleteKey `POST /v2/DeleteKey?id=<acces key id>`
Deletes an API access key.
### Bucket operations
#### ListBuckets `GET /v1/bucket`
#### ListBuckets `GET /v2/ListBuckets`
Returns all storage buckets in the cluster.
@ -572,8 +580,8 @@ Example response:
]
```
#### GetBucketInfo `GET /v1/bucket?id=<bucket id>`
#### GetBucketInfo `GET /v1/bucket?globalAlias=<alias>`
#### GetBucketInfo `GET /v2/GetBucketInfo?id=<bucket id>`
#### GetBucketInfo `GET /v2/GetBucketInfo?globalAlias=<alias>`
Returns information about the requested storage bucket.
@ -616,7 +624,7 @@ Example response:
}
```
#### CreateBucket `POST /v1/bucket`
#### CreateBucket `POST /v2/CreateBucket`
Creates a new storage bucket.
@ -656,7 +664,7 @@ or no alias at all.
Technically, you can also specify both `globalAlias` and `localAlias` and that would create
two aliases, but I don't see why you would want to do that.
#### UpdateBucket `PUT /v1/bucket?id=<bucket id>`
#### UpdateBucket `POST /v2/UpdateBucket?id=<bucket id>`
Updates configuration of the given bucket.
@ -688,7 +696,7 @@ In `quotas`: new values of `maxSize` and `maxObjects` must both be specified, or
to remove the quotas. An absent value will be considered the same as a `null`. It is not possible
to change only one of the two quotas.
#### DeleteBucket `DELETE /v1/bucket?id=<bucket id>`
#### DeleteBucket `POST /v2/DeleteBucket?id=<bucket id>`
Deletes a storage bucket. A bucket cannot be deleted if it is not empty.
@ -697,7 +705,7 @@ Warning: this will delete all aliases associated with the bucket!
### Operations on permissions for keys on buckets
#### BucketAllowKey `POST /v1/bucket/allow`
#### BucketAllowKey `POST /v2/BucketAllowKey`
Allows a key to do read/write/owner operations on a bucket.
@ -718,7 +726,7 @@ Request body format:
Flags in `permissions` which have the value `true` will be activated.
Other flags will remain unchanged.
#### BucketDenyKey `POST /v1/bucket/deny`
#### BucketDenyKey `POST /v2/BucketDenyKey`
Denies a key from doing read/write/owner operations on a bucket.
@ -742,19 +750,57 @@ Other flags will remain unchanged.
### Operations on bucket aliases
#### GlobalAliasBucket `PUT /v1/bucket/alias/global?id=<bucket id>&alias=<global alias>`
#### GlobalAliasBucket `POST /v2/GlobalAliasBucket`
Empty body. Creates a global alias for a bucket.
Creates a global alias for a bucket.
#### GlobalUnaliasBucket `DELETE /v1/bucket/alias/global?id=<bucket id>&alias=<global alias>`
Request body format:
```json
{
"bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
"alias": "the-bucket"
}
```
#### GlobalUnaliasBucket `POST /v2/GlobalUnaliasBucket`
Removes a global alias for a bucket.
#### LocalAliasBucket `PUT /v1/bucket/alias/local?id=<bucket id>&accessKeyId=<access key ID>&alias=<local alias>`
Request body format:
Empty body. Creates a local alias for a bucket in the namespace of a specific access key.
```json
{
"bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
"alias": "the-bucket"
}
```
#### LocalUnaliasBucket `DELETE /v1/bucket/alias/local?id=<bucket id>&accessKeyId<access key ID>&alias=<local alias>`
#### LocalAliasBucket `POST /v2/LocalAliasBucket`
Creates a local alias for a bucket in the namespace of a specific access key.
Request body format:
```json
{
"bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
"accessKeyId": "GK31c2f218a2e44f485b94239e",
"alias": "my-bucket"
}
```
#### LocalUnaliasBucket `POST /v2/LocalUnaliasBucket`
Removes a local alias for a bucket in the namespace of a specific access key.
Request body format:
```json
{
"bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
"accessKeyId": "GK31c2f218a2e44f485b94239e",
"alias": "my-bucket"
}
```

View file

@ -500,8 +500,9 @@ pub struct BucketDenyKeyResponse(pub GetBucketInfoResponse);
// ---- GlobalAliasBucket ----
#[derive(Deserialize)]
pub struct GlobalAliasBucketRequest {
pub id: String,
pub bucket_id: String,
pub alias: String,
}
@ -510,8 +511,9 @@ pub struct GlobalAliasBucketResponse(pub GetBucketInfoResponse);
// ---- GlobalUnaliasBucket ----
#[derive(Deserialize)]
pub struct GlobalUnaliasBucketRequest {
pub id: String,
pub bucket_id: String,
pub alias: String,
}
@ -520,8 +522,9 @@ pub struct GlobalUnaliasBucketResponse(pub GetBucketInfoResponse);
// ---- LocalAliasBucket ----
#[derive(Deserialize)]
pub struct LocalAliasBucketRequest {
pub id: String,
pub bucket_id: String,
pub access_key_id: String,
pub alias: String,
}
@ -531,8 +534,9 @@ pub struct LocalAliasBucketResponse(pub GetBucketInfoResponse);
// ---- LocalUnaliasBucket ----
#[derive(Deserialize)]
pub struct LocalUnaliasBucketRequest {
pub id: String,
pub bucket_id: String,
pub access_key_id: String,
pub alias: String,
}

View file

@ -39,7 +39,7 @@ pub struct AdminApiServer {
admin_token: Option<String>,
}
enum Endpoint {
pub enum Endpoint {
Old(router_v1::Endpoint),
New(String),
}
@ -159,7 +159,7 @@ impl ApiHandler for AdminApiServer {
AdminApiRequest::Options(req) => req.handle(&self.garage).await,
AdminApiRequest::CheckDomain(req) => req.handle(&self.garage).await,
AdminApiRequest::Health(req) => req.handle(&self.garage).await,
AdminApiRequest::Metrics(req) => self.handle_metrics(),
AdminApiRequest::Metrics(_req) => self.handle_metrics(),
req => {
let res = req.handle(&self.garage).await?;
json_ok_response(&res)

View file

@ -457,7 +457,7 @@ impl EndpointHandler for GlobalAliasBucketRequest {
type Response = GlobalAliasBucketResponse;
async fn handle(self, garage: &Arc<Garage>) -> Result<GlobalAliasBucketResponse, Error> {
let bucket_id = parse_bucket_id(&self.id)?;
let bucket_id = parse_bucket_id(&self.bucket_id)?;
let helper = garage.locked_helper().await;
@ -476,7 +476,7 @@ impl EndpointHandler for GlobalUnaliasBucketRequest {
type Response = GlobalUnaliasBucketResponse;
async fn handle(self, garage: &Arc<Garage>) -> Result<GlobalUnaliasBucketResponse, Error> {
let bucket_id = parse_bucket_id(&self.id)?;
let bucket_id = parse_bucket_id(&self.bucket_id)?;
let helper = garage.locked_helper().await;
@ -495,7 +495,7 @@ impl EndpointHandler for LocalAliasBucketRequest {
type Response = LocalAliasBucketResponse;
async fn handle(self, garage: &Arc<Garage>) -> Result<LocalAliasBucketResponse, Error> {
let bucket_id = parse_bucket_id(&self.id)?;
let bucket_id = parse_bucket_id(&self.bucket_id)?;
let helper = garage.locked_helper().await;
@ -514,7 +514,7 @@ impl EndpointHandler for LocalUnaliasBucketRequest {
type Response = LocalUnaliasBucketResponse;
async fn handle(self, garage: &Arc<Garage>) -> Result<LocalUnaliasBucketResponse, Error> {
let bucket_id = parse_bucket_id(&self.id)?;
let bucket_id = parse_bucket_id(&self.bucket_id)?;
let helper = garage.locked_helper().await;

View file

@ -22,7 +22,7 @@ macro_rules! admin_endpoints {
}
impl AdminApiRequest {
fn name(&self) -> &'static str {
pub fn name(&self) -> &'static str {
match self {
$(
Self::$special_endpoint(_) => stringify!($special_endpoint),

View file

@ -43,22 +43,22 @@ impl AdminApiRequest {
POST UpdateKey (body_field, query::id),
POST CreateKey (body),
POST ImportKey (body),
DELETE DeleteKey (query::id),
POST DeleteKey (query::id),
GET ListKeys (),
// Bucket endpoints
GET GetBucketInfo (query_opt::id, query_opt::global_alias),
GET ListBuckets (),
POST CreateBucket (body),
DELETE DeleteBucket (query::id),
PUT UpdateBucket (body_field, query::id),
POST DeleteBucket (query::id),
POST UpdateBucket (body_field, query::id),
// Bucket-key permissions
POST BucketAllowKey (body),
POST BucketDenyKey (body),
// Bucket aliases
PUT GlobalAliasBucket (query::id, query::alias),
DELETE GlobalUnaliasBucket (query::id, query::alias),
PUT LocalAliasBucket (query::id, query::access_key_id, query::alias),
DELETE LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
POST GlobalAliasBucket (body),
POST GlobalUnaliasBucket (body),
POST LocalAliasBucket (body),
POST LocalUnaliasBucket (body),
]);
if let Some(message) = query.nonempty_message() {
@ -131,7 +131,11 @@ impl AdminApiRequest {
let body = parse_json_body::<UpdateKeyRequestBody, _, Error>(req).await?;
Ok(AdminApiRequest::UpdateKey(UpdateKeyRequest { id, body }))
}
Endpoint::DeleteKey { id } => Ok(AdminApiRequest::DeleteKey(DeleteKeyRequest { id })),
// DeleteKey semantics changed:
// - in v1/ : HTTP DELETE => HTTP 204 No Content
// - in v2/ : HTTP POST => HTTP 200 Ok
// Endpoint::DeleteKey { id } => Ok(AdminApiRequest::DeleteKey(DeleteKeyRequest { id })),
// Buckets
Endpoint::ListBuckets => Ok(AdminApiRequest::ListBuckets(ListBucketsRequest)),
@ -145,9 +149,13 @@ impl AdminApiRequest {
let req = parse_json_body::<CreateBucketRequest, _, Error>(req).await?;
Ok(AdminApiRequest::CreateBucket(req))
}
Endpoint::DeleteBucket { id } => {
Ok(AdminApiRequest::DeleteBucket(DeleteBucketRequest { id }))
}
// DeleteBucket semantics changed::
// - in v1/ : HTTP DELETE => HTTP 204 No Content
// - in v2/ : HTTP POST => HTTP 200 Ok
// Endpoint::DeleteBucket { id } => {
// Ok(AdminApiRequest::DeleteBucket(DeleteBucketRequest { id }))
// }
Endpoint::UpdateBucket { id } => {
let body = parse_json_body::<UpdateBucketRequestBody, _, Error>(req).await?;
Ok(AdminApiRequest::UpdateBucket(UpdateBucketRequest {
@ -167,10 +175,16 @@ impl AdminApiRequest {
}
// Bucket aliasing
Endpoint::GlobalAliasBucket { id, alias } => Ok(AdminApiRequest::GlobalAliasBucket(
GlobalAliasBucketRequest { id, alias },
GlobalAliasBucketRequest {
bucket_id: id,
alias,
},
)),
Endpoint::GlobalUnaliasBucket { id, alias } => Ok(
AdminApiRequest::GlobalUnaliasBucket(GlobalUnaliasBucketRequest { id, alias }),
AdminApiRequest::GlobalUnaliasBucket(GlobalUnaliasBucketRequest {
bucket_id: id,
alias,
}),
),
Endpoint::LocalAliasBucket {
id,
@ -178,7 +192,7 @@ impl AdminApiRequest {
alias,
} => Ok(AdminApiRequest::LocalAliasBucket(LocalAliasBucketRequest {
access_key_id,
id,
bucket_id: id,
alias,
})),
Endpoint::LocalUnaliasBucket {
@ -188,7 +202,7 @@ impl AdminApiRequest {
} => Ok(AdminApiRequest::LocalUnaliasBucket(
LocalUnaliasBucketRequest {
access_key_id,
id,
bucket_id: id,
alias,
},
)),