mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-04 03:47:42 +00:00
admin api: implement InspectObject (fix #892)
This commit is contained in:
@@ -80,6 +80,7 @@ admin_endpoints![
|
||||
UpdateBucket,
|
||||
DeleteBucket,
|
||||
CleanupIncompleteUploads,
|
||||
InspectObject,
|
||||
|
||||
// Operations on permissions for keys on buckets
|
||||
AllowBucketKey,
|
||||
@@ -907,6 +908,48 @@ pub struct CleanupIncompleteUploadsResponse {
|
||||
pub uploads_deleted: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, IntoParams)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InspectObjectRequest {
|
||||
pub bucket_id: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InspectObjectResponse {
|
||||
pub bucket_id: String,
|
||||
pub key: String,
|
||||
pub versions: Vec<InspectObjectVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InspectObjectVersion {
|
||||
pub uuid: String,
|
||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
||||
pub encrypted: bool,
|
||||
pub uploading: bool,
|
||||
pub aborted: bool,
|
||||
pub delete_marker: bool,
|
||||
pub inline: bool,
|
||||
pub size: Option<u64>,
|
||||
pub etag: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub headers: Vec<(String, String)>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub blocks: Vec<InspectObjectBlock>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InspectObjectBlock {
|
||||
pub part_number: u64,
|
||||
pub offset: u64,
|
||||
pub hash: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
// **********************************************
|
||||
// Operations on permissions for keys on buckets
|
||||
// **********************************************
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::DateTime;
|
||||
|
||||
use garage_util::crdt::*;
|
||||
use garage_util::data::*;
|
||||
use garage_util::time::*;
|
||||
@@ -349,6 +351,127 @@ impl RequestHandler for CleanupIncompleteUploadsRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestHandler for InspectObjectRequest {
|
||||
type Response = InspectObjectResponse;
|
||||
|
||||
async fn handle(
|
||||
self,
|
||||
garage: &Arc<Garage>,
|
||||
_admin: &Admin,
|
||||
) -> Result<InspectObjectResponse, Error> {
|
||||
let bucket_id = parse_bucket_id(&self.bucket_id)?;
|
||||
|
||||
let object = garage
|
||||
.object_table
|
||||
.get(&bucket_id, &self.key)
|
||||
.await?
|
||||
.ok_or_else(|| Error::bad_request("object not found"))?;
|
||||
|
||||
let mut versions = vec![];
|
||||
for obj_ver in object.versions().iter() {
|
||||
let ver = garage.version_table.get(&obj_ver.uuid, &EmptyKey).await?;
|
||||
let blocks = ver
|
||||
.map(|v| {
|
||||
v.blocks
|
||||
.items()
|
||||
.iter()
|
||||
.map(|(vk, vb)| InspectObjectBlock {
|
||||
part_number: vk.part_number,
|
||||
offset: vk.offset,
|
||||
hash: hex::encode(&vb.hash),
|
||||
size: vb.size,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let uuid = hex::encode(&obj_ver.uuid);
|
||||
let timestamp = DateTime::from_timestamp_millis(obj_ver.timestamp as i64)
|
||||
.expect("invalid timestamp in db");
|
||||
match &obj_ver.state {
|
||||
ObjectVersionState::Uploading { encryption, .. } => {
|
||||
versions.push(InspectObjectVersion {
|
||||
uuid,
|
||||
timestamp,
|
||||
encrypted: !matches!(encryption, ObjectVersionEncryption::Plaintext { .. }),
|
||||
uploading: true,
|
||||
headers: match encryption {
|
||||
ObjectVersionEncryption::Plaintext { inner } => inner.headers.clone(),
|
||||
_ => vec![],
|
||||
},
|
||||
blocks,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
ObjectVersionState::Complete(data) => match data {
|
||||
ObjectVersionData::DeleteMarker => {
|
||||
versions.push(InspectObjectVersion {
|
||||
uuid,
|
||||
timestamp,
|
||||
delete_marker: true,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
ObjectVersionData::Inline(meta, _) => {
|
||||
versions.push(InspectObjectVersion {
|
||||
uuid,
|
||||
timestamp,
|
||||
inline: true,
|
||||
size: Some(meta.size),
|
||||
etag: Some(meta.etag.clone()),
|
||||
encrypted: !matches!(
|
||||
meta.encryption,
|
||||
ObjectVersionEncryption::Plaintext { .. }
|
||||
),
|
||||
headers: match &meta.encryption {
|
||||
ObjectVersionEncryption::Plaintext { inner } => {
|
||||
inner.headers.clone()
|
||||
}
|
||||
_ => vec![],
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
ObjectVersionData::FirstBlock(meta, _) => {
|
||||
versions.push(InspectObjectVersion {
|
||||
uuid,
|
||||
timestamp,
|
||||
size: Some(meta.size),
|
||||
etag: Some(meta.etag.clone()),
|
||||
encrypted: !matches!(
|
||||
meta.encryption,
|
||||
ObjectVersionEncryption::Plaintext { .. }
|
||||
),
|
||||
headers: match &meta.encryption {
|
||||
ObjectVersionEncryption::Plaintext { inner } => {
|
||||
inner.headers.clone()
|
||||
}
|
||||
_ => vec![],
|
||||
},
|
||||
blocks,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
},
|
||||
ObjectVersionState::Aborted => {
|
||||
versions.push(InspectObjectVersion {
|
||||
uuid,
|
||||
timestamp,
|
||||
aborted: true,
|
||||
blocks,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InspectObjectResponse {
|
||||
bucket_id: hex::encode(&object.bucket_id),
|
||||
key: object.key,
|
||||
versions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---- BUCKET/KEY PERMISSIONS ----
|
||||
|
||||
impl RequestHandler for AllowBucketKeyRequest {
|
||||
|
||||
@@ -509,6 +509,20 @@ fn DeleteBucket() -> () {}
|
||||
)]
|
||||
fn CleanupIncompleteUploads() -> () {}
|
||||
|
||||
#[utoipa::path(get,
|
||||
path = "/v2/InspectObject",
|
||||
tag = "Bucket",
|
||||
description = "
|
||||
Returns detailed information about an object in a bucket, including its internal state in Garage.
|
||||
",
|
||||
params(InspectObjectRequest),
|
||||
responses(
|
||||
(status = 200, description = "Returns exhaustive information about the object", body = InspectObjectResponse),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
)]
|
||||
fn InspectObject() -> () {}
|
||||
|
||||
// **********************************************
|
||||
// Operations on permissions for keys on buckets
|
||||
// **********************************************
|
||||
@@ -872,6 +886,7 @@ impl Modify for SecurityAddon {
|
||||
UpdateBucket,
|
||||
DeleteBucket,
|
||||
CleanupIncompleteUploads,
|
||||
InspectObject,
|
||||
// Operations on permissions
|
||||
AllowBucketKey,
|
||||
DenyBucketKey,
|
||||
|
||||
@@ -62,6 +62,7 @@ impl AdminApiRequest {
|
||||
POST DeleteBucket (query::id),
|
||||
POST UpdateBucket (body_field, query::id),
|
||||
POST CleanupIncompleteUploads (body),
|
||||
GET InspectObject (query::bucket_id, query::key),
|
||||
// Bucket-key permissions
|
||||
POST AllowBucketKey (body),
|
||||
POST DenyBucketKey (body),
|
||||
@@ -267,6 +268,8 @@ generateQueryParameters! {
|
||||
"globalAlias" => global_alias,
|
||||
"alias" => alias,
|
||||
"accessKeyId" => access_key_id,
|
||||
"showSecretKey" => show_secret_key
|
||||
"showSecretKey" => show_secret_key,
|
||||
"bucketId" => bucket_id,
|
||||
"key" => key
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user