diff --git a/doc/api/garage-admin-v2.json b/doc/api/garage-admin-v2.json index 7819a0a6..364d170b 100644 --- a/doc/api/garage-admin-v2.json +++ b/doc/api/garage-admin-v2.json @@ -2280,6 +2280,7 @@ "type": "object", "required": [ "id", + "created", "globalAliases", "websiteAccess", "keys", @@ -2297,6 +2298,11 @@ "format": "int64", "description": "Total number of bytes used by objects in this bucket" }, + "created": { + "type": "string", + "format": "date-time", + "description": "Bucket creation date" + }, "globalAliases": { "type": "array", "items": { @@ -2873,10 +2879,15 @@ "type": "object", "required": [ "id", + "created", "globalAliases", "localAliases" ], "properties": { + "created": { + "type": "string", + "format": "date-time" + }, "globalAliases": { "type": "array", "items": { diff --git a/src/api/admin/api.rs b/src/api/admin/api.rs index ffb9456b..d2daa988 100644 --- a/src/api/admin/api.rs +++ b/src/api/admin/api.rs @@ -3,6 +3,7 @@ use std::convert::TryFrom; use std::net::SocketAddr; use std::sync::Arc; +use chrono::{DateTime, Utc}; use paste::paste; use serde::{Deserialize, Serialize}; use utoipa::{IntoParams, ToSchema}; @@ -321,11 +322,11 @@ pub struct GetAdminTokenInfoResponse { /// Identifier of the admin token (which is also a prefix of the full bearer token) pub id: Option, /// Creation date - pub created: Option>, + pub created: Option>, /// Name of the admin API token pub name: String, /// Expiration time and date, formatted according to RFC 3339 - pub expiration: Option>, + pub expiration: Option>, /// Whether this admin token is expired already pub expired: bool, /// Scope of the admin API token, a list of admin endpoint names (such as @@ -364,7 +365,7 @@ pub struct UpdateAdminTokenRequestBody { /// Name of the admin API token pub name: Option, /// Expiration time and date, formatted according to RFC 3339 - pub expiration: Option>, + pub expiration: Option>, /// Scope of the admin API token, a list of admin endpoint names (such as /// `GetClusterStatus`, etc), or the special value `*` to allow all /// admin endpoints. **WARNING:** Granting a scope of `CreateAdminToken` or @@ -759,6 +760,7 @@ pub struct ListBucketsResponse(pub Vec); #[serde(rename_all = "camelCase")] pub struct ListBucketsResponseItem { pub id: String, + pub created: DateTime, pub global_aliases: Vec, pub local_aliases: Vec, } @@ -788,6 +790,8 @@ pub struct GetBucketInfoRequest { pub struct GetBucketInfoResponse { /// Identifier of the bucket pub id: String, + /// Bucket creation date + pub created: DateTime, /// List of global aliases for this bucket pub global_aliases: Vec, /// Whether website acces is enabled for this bucket @@ -932,7 +936,7 @@ pub struct InspectObjectVersion { /// Version ID pub uuid: String, /// Creation timestamp of this object version - pub timestamp: chrono::DateTime, + pub timestamp: DateTime, /// Whether this object version was created with SSE-C encryption pub encrypted: bool, /// Whether this object version is still uploading diff --git a/src/api/admin/bucket.rs b/src/api/admin/bucket.rs index af26200b..b0fd101b 100644 --- a/src/api/admin/bucket.rs +++ b/src/api/admin/bucket.rs @@ -48,6 +48,8 @@ impl RequestHandler for ListBucketsRequest { let state = b.state.as_option().unwrap(); ListBucketsResponseItem { id: hex::encode(b.id), + created: DateTime::from_timestamp_millis(state.creation_date as i64) + .expect("invalid timestamp stored in db"), global_aliases: state .aliases .items() @@ -677,6 +679,8 @@ async fn bucket_info_results( let quotas = state.quotas.get(); let res = GetBucketInfoResponse { id: hex::encode(bucket.id), + created: DateTime::from_timestamp_millis(state.creation_date as i64) + .expect("invalid timestamp stored in db"), global_aliases: state .aliases .items() diff --git a/src/garage/cli/remote/bucket.rs b/src/garage/cli/remote/bucket.rs index bc018b33..1c0774a3 100644 --- a/src/garage/cli/remote/bucket.rs +++ b/src/garage/cli/remote/bucket.rs @@ -1,6 +1,8 @@ //use bytesize::ByteSize; use format_table::format_table; +use chrono::Local; + use garage_util::error::*; use garage_api_admin::api::*; @@ -29,13 +31,16 @@ impl Cli { } pub async fn cmd_list_buckets(&self) -> Result<(), Error> { - let buckets = self.api_request(ListBucketsRequest).await?; + let mut buckets = self.api_request(ListBucketsRequest).await?; - let mut table = vec!["ID\tGlobal aliases\tLocal aliases".to_string()]; + buckets.0.sort_by_key(|x| x.created); + + let mut table = vec!["ID\tCreated\tGlobal aliases\tLocal aliases".to_string()]; for bucket in buckets.0.iter() { table.push(format!( - "{:.16}\t{}\t{}", + "{:.16}\t{}\t{}\t{}", bucket.id, + bucket.created.with_timezone(&Local).date_naive(), table_list_abbr(&bucket.global_aliases), table_list_abbr( bucket @@ -484,6 +489,7 @@ fn print_bucket_info(bucket: &GetBucketInfoResponse) { let mut info = vec![ format!("Bucket:\t{}", bucket.id), + format!("Created:\t{}", bucket.created.with_timezone(&Local)), String::new(), { let size = bytesize::ByteSize::b(bucket.bytes as u64);