mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-11 06:46:53 +00:00
New model for buckets
This commit is contained in:
+4
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "garage_api"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3.0"
|
||||
@@ -14,9 +14,9 @@ path = "lib.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
garage_model = { version = "0.5.0", path = "../model" }
|
||||
garage_table = { version = "0.5.0", path = "../table" }
|
||||
garage_util = { version = "0.5.0", path = "../util" }
|
||||
garage_model = { version = "0.6.0", path = "../model" }
|
||||
garage_table = { version = "0.6.0", path = "../table" }
|
||||
garage_util = { version = "0.6.0", path = "../util" }
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "1.0"
|
||||
|
||||
+83
-26
@@ -7,9 +7,12 @@ use hyper::server::conn::AddrStream;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
|
||||
use garage_util::crdt;
|
||||
use garage_util::data::*;
|
||||
use garage_util::error::Error as GarageError;
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::key_table::Key;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::signature::check_signature;
|
||||
@@ -105,10 +108,20 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
.and_then(|root_domain| host_to_bucket(&host, root_domain));
|
||||
|
||||
let endpoint = Endpoint::from_request(&req, bucket.map(ToOwned::to_owned))?;
|
||||
|
||||
let bucket_name = match endpoint.authorization_type() {
|
||||
Authorization::None => {
|
||||
return handle_request_without_bucket(garage, req, api_key, endpoint).await
|
||||
}
|
||||
Authorization::Read(bucket) | Authorization::Write(bucket) => bucket.to_string(),
|
||||
};
|
||||
|
||||
let bucket_id = resolve_bucket(&garage, &bucket_name, &api_key).await?;
|
||||
|
||||
let allowed = match endpoint.authorization_type() {
|
||||
Authorization::None => true,
|
||||
Authorization::Read(bucket) => api_key.allow_read(bucket),
|
||||
Authorization::Write(bucket) => api_key.allow_write(bucket),
|
||||
Authorization::Read(_) => api_key.allow_read(&bucket_id),
|
||||
Authorization::Write(_) => api_key.allow_write(&bucket_id),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if !allowed {
|
||||
@@ -118,19 +131,18 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
}
|
||||
|
||||
match endpoint {
|
||||
Endpoint::ListBuckets => handle_list_buckets(&api_key),
|
||||
Endpoint::HeadObject { bucket, key, .. } => handle_head(garage, &req, &bucket, &key).await,
|
||||
Endpoint::GetObject { bucket, key, .. } => handle_get(garage, &req, &bucket, &key).await,
|
||||
Endpoint::HeadObject { key, .. } => handle_head(garage, &req, bucket_id, &key).await,
|
||||
Endpoint::GetObject { key, .. } => handle_get(garage, &req, bucket_id, &key).await,
|
||||
Endpoint::UploadPart {
|
||||
bucket,
|
||||
key,
|
||||
part_number,
|
||||
upload_id,
|
||||
..
|
||||
} => {
|
||||
handle_put_part(
|
||||
garage,
|
||||
req,
|
||||
&bucket,
|
||||
bucket_id,
|
||||
&key,
|
||||
part_number,
|
||||
&upload_id,
|
||||
@@ -138,38 +150,46 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
)
|
||||
.await
|
||||
}
|
||||
Endpoint::CopyObject { bucket, key } => {
|
||||
Endpoint::CopyObject { key, .. } => {
|
||||
let copy_source = req.headers().get("x-amz-copy-source").unwrap().to_str()?;
|
||||
let copy_source = percent_encoding::percent_decode_str(copy_source).decode_utf8()?;
|
||||
let (source_bucket, source_key) = parse_bucket_key(©_source, None)?;
|
||||
if !api_key.allow_read(source_bucket) {
|
||||
let source_bucket_id =
|
||||
resolve_bucket(&garage, &source_bucket.to_string(), &api_key).await?;
|
||||
if !api_key.allow_read(&source_bucket_id) {
|
||||
return Err(Error::Forbidden(format!(
|
||||
"Reading from bucket {} not allowed for this key",
|
||||
source_bucket
|
||||
)));
|
||||
}
|
||||
let source_key = source_key.ok_or_bad_request("No source key specified")?;
|
||||
handle_copy(garage, &req, &bucket, &key, source_bucket, source_key).await
|
||||
handle_copy(garage, &req, bucket_id, &key, source_bucket_id, source_key).await
|
||||
}
|
||||
Endpoint::PutObject { bucket, key } => {
|
||||
handle_put(garage, req, &bucket, &key, content_sha256).await
|
||||
Endpoint::PutObject { key, .. } => {
|
||||
handle_put(garage, req, bucket_id, &key, content_sha256).await
|
||||
}
|
||||
Endpoint::AbortMultipartUpload {
|
||||
bucket,
|
||||
key,
|
||||
upload_id,
|
||||
} => handle_abort_multipart_upload(garage, &bucket, &key, &upload_id).await,
|
||||
Endpoint::DeleteObject { bucket, key, .. } => handle_delete(garage, &bucket, &key).await,
|
||||
Endpoint::AbortMultipartUpload { key, upload_id, .. } => {
|
||||
handle_abort_multipart_upload(garage, bucket_id, &key, &upload_id).await
|
||||
}
|
||||
Endpoint::DeleteObject { key, .. } => handle_delete(garage, bucket_id, &key).await,
|
||||
Endpoint::CreateMultipartUpload { bucket, key } => {
|
||||
handle_create_multipart_upload(garage, &req, &bucket, &key).await
|
||||
handle_create_multipart_upload(garage, &req, &bucket, bucket_id, &key).await
|
||||
}
|
||||
Endpoint::CompleteMultipartUpload {
|
||||
bucket,
|
||||
key,
|
||||
upload_id,
|
||||
} => {
|
||||
handle_complete_multipart_upload(garage, req, &bucket, &key, &upload_id, content_sha256)
|
||||
.await
|
||||
handle_complete_multipart_upload(
|
||||
garage,
|
||||
req,
|
||||
&bucket,
|
||||
bucket_id,
|
||||
&key,
|
||||
&upload_id,
|
||||
content_sha256,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Endpoint::CreateBucket { bucket } => {
|
||||
debug!(
|
||||
@@ -206,7 +226,8 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
garage,
|
||||
&ListObjectsQuery {
|
||||
is_v2: false,
|
||||
bucket,
|
||||
bucket_name: bucket,
|
||||
bucket_id,
|
||||
delimiter: delimiter.map(|d| d.to_string()),
|
||||
max_keys: max_keys.unwrap_or(1000),
|
||||
prefix: prefix.unwrap_or_default(),
|
||||
@@ -234,7 +255,8 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
garage,
|
||||
&ListObjectsQuery {
|
||||
is_v2: true,
|
||||
bucket,
|
||||
bucket_name: bucket,
|
||||
bucket_id,
|
||||
delimiter: delimiter.map(|d| d.to_string()),
|
||||
max_keys: max_keys.unwrap_or(1000),
|
||||
prefix: prefix.unwrap_or_default(),
|
||||
@@ -252,8 +274,8 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
)))
|
||||
}
|
||||
}
|
||||
Endpoint::DeleteObjects { bucket } => {
|
||||
handle_delete_objects(garage, &bucket, req, content_sha256).await
|
||||
Endpoint::DeleteObjects { .. } => {
|
||||
handle_delete_objects(garage, bucket_id, req, content_sha256).await
|
||||
}
|
||||
Endpoint::PutBucketWebsite { bucket } => {
|
||||
handle_put_website(garage, bucket, req, content_sha256).await
|
||||
@@ -263,6 +285,41 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request_without_bucket(
|
||||
garage: Arc<Garage>,
|
||||
_req: Request<Body>,
|
||||
api_key: Key,
|
||||
endpoint: Endpoint,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
match endpoint {
|
||||
Endpoint::ListBuckets => handle_list_buckets(&garage, &api_key).await,
|
||||
endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
async fn resolve_bucket(
|
||||
garage: &Garage,
|
||||
bucket_name: &String,
|
||||
api_key: &Key,
|
||||
) -> Result<Uuid, Error> {
|
||||
let api_key_params = api_key
|
||||
.state
|
||||
.as_option()
|
||||
.ok_or_else(|| Error::Forbidden("Operation is not allowed for this key.".to_string()))?;
|
||||
|
||||
if let Some(crdt::Deletable::Present(bucket_id)) = api_key_params.local_aliases.get(bucket_name)
|
||||
{
|
||||
Ok(*bucket_id)
|
||||
} else {
|
||||
Ok(garage
|
||||
.bucket_helper()
|
||||
.resolve_global_bucket_name(bucket_name)
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the bucket name and the key name from an HTTP path and possibly a bucket provided in
|
||||
/// the host header of the request
|
||||
///
|
||||
|
||||
+55
-7
@@ -1,9 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper::{Body, Response};
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::key_table::Key;
|
||||
use garage_table::util::EmptyKey;
|
||||
use garage_util::crdt::*;
|
||||
use garage_util::time::*;
|
||||
|
||||
use crate::error::*;
|
||||
@@ -34,20 +37,65 @@ pub fn handle_get_bucket_versioning() -> Result<Response<Body>, Error> {
|
||||
.body(Body::from(xml.into_bytes()))?)
|
||||
}
|
||||
|
||||
pub fn handle_list_buckets(api_key: &Key) -> Result<Response<Body>, Error> {
|
||||
pub async fn handle_list_buckets(garage: &Garage, api_key: &Key) -> Result<Response<Body>, Error> {
|
||||
let key_state = api_key.state.as_option().ok_or_internal_error(
|
||||
"Key should not be in deleted state at this point (internal error)",
|
||||
)?;
|
||||
|
||||
// Collect buckets user has access to
|
||||
let ids = api_key
|
||||
.state
|
||||
.as_option()
|
||||
.unwrap()
|
||||
.authorized_buckets
|
||||
.items()
|
||||
.iter()
|
||||
.filter(|(_, perms)| perms.allow_read || perms.allow_write)
|
||||
.map(|(id, _)| *id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut buckets_by_id = HashMap::new();
|
||||
let mut aliases = HashMap::new();
|
||||
|
||||
for bucket_id in ids.iter() {
|
||||
let bucket = garage.bucket_table.get(bucket_id, &EmptyKey).await?;
|
||||
if let Some(bucket) = bucket {
|
||||
if let Deletable::Present(param) = bucket.state {
|
||||
for (alias, _, active) in param.aliases.items() {
|
||||
if *active {
|
||||
let alias_ent = garage.bucket_alias_table.get(&EmptyKey, alias).await?;
|
||||
if let Some(alias_ent) = alias_ent {
|
||||
if let Some(alias_p) = alias_ent.state.get().as_option() {
|
||||
if alias_p.bucket_id == *bucket_id {
|
||||
aliases.insert(alias_ent.name.clone(), *bucket_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buckets_by_id.insert(bucket_id, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (alias, _, id) in key_state.local_aliases.items() {
|
||||
if let Some(id) = id.as_option() {
|
||||
aliases.insert(alias.clone(), *id);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate response
|
||||
let list_buckets = s3_xml::ListAllMyBucketsResult {
|
||||
owner: s3_xml::Owner {
|
||||
display_name: s3_xml::Value(api_key.name.get().to_string()),
|
||||
id: s3_xml::Value(api_key.key_id.to_string()),
|
||||
},
|
||||
buckets: s3_xml::BucketList {
|
||||
entries: api_key
|
||||
.authorized_buckets
|
||||
.items()
|
||||
entries: aliases
|
||||
.iter()
|
||||
.filter(|(_, _, perms)| perms.allow_read || perms.allow_write)
|
||||
.map(|(name, ts, _)| s3_xml::Bucket {
|
||||
creation_date: s3_xml::Value(msec_to_rfc3339(*ts)),
|
||||
.filter_map(|(name, id)| buckets_by_id.get(id).map(|p| (name, id, p)))
|
||||
.map(|(name, _id, param)| s3_xml::Bucket {
|
||||
creation_date: s3_xml::Value(msec_to_rfc3339(param.creation_date)),
|
||||
name: s3_xml::Value(name.to_string()),
|
||||
})
|
||||
.collect(),
|
||||
|
||||
+8
-12
@@ -18,14 +18,14 @@ use crate::s3_xml;
|
||||
pub async fn handle_copy(
|
||||
garage: Arc<Garage>,
|
||||
req: &Request<Body>,
|
||||
dest_bucket: &str,
|
||||
dest_bucket_id: Uuid,
|
||||
dest_key: &str,
|
||||
source_bucket: &str,
|
||||
source_bucket_id: Uuid,
|
||||
source_key: &str,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let source_object = garage
|
||||
.object_table
|
||||
.get(&source_bucket.to_string(), &source_key.to_string())
|
||||
.get(&source_bucket_id, &source_key.to_string())
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
|
||||
@@ -76,7 +76,7 @@ pub async fn handle_copy(
|
||||
)),
|
||||
};
|
||||
let dest_object = Object::new(
|
||||
dest_bucket.to_string(),
|
||||
dest_bucket_id,
|
||||
dest_key.to_string(),
|
||||
vec![dest_object_version],
|
||||
);
|
||||
@@ -99,7 +99,7 @@ pub async fn handle_copy(
|
||||
state: ObjectVersionState::Uploading(new_meta.headers.clone()),
|
||||
};
|
||||
let tmp_dest_object = Object::new(
|
||||
dest_bucket.to_string(),
|
||||
dest_bucket_id,
|
||||
dest_key.to_string(),
|
||||
vec![tmp_dest_object_version],
|
||||
);
|
||||
@@ -109,12 +109,8 @@ pub async fn handle_copy(
|
||||
// this means that the BlockRef entries linked to this version cannot be
|
||||
// marked as deleted (they are marked as deleted only if the Version
|
||||
// doesn't exist or is marked as deleted).
|
||||
let mut dest_version = Version::new(
|
||||
new_uuid,
|
||||
dest_bucket.to_string(),
|
||||
dest_key.to_string(),
|
||||
false,
|
||||
);
|
||||
let mut dest_version =
|
||||
Version::new(new_uuid, dest_bucket_id, dest_key.to_string(), false);
|
||||
garage.version_table.insert(&dest_version).await?;
|
||||
|
||||
// Fill in block list for version and insert block refs
|
||||
@@ -151,7 +147,7 @@ pub async fn handle_copy(
|
||||
)),
|
||||
};
|
||||
let dest_object = Object::new(
|
||||
dest_bucket.to_string(),
|
||||
dest_bucket_id,
|
||||
dest_key.to_string(),
|
||||
vec![dest_object_version],
|
||||
);
|
||||
|
||||
@@ -14,12 +14,12 @@ use crate::signature::verify_signed_content;
|
||||
|
||||
async fn handle_delete_internal(
|
||||
garage: &Garage,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
) -> Result<(Uuid, Uuid), Error> {
|
||||
let object = garage
|
||||
.object_table
|
||||
.get(&bucket.to_string(), &key.to_string())
|
||||
.get(&bucket_id, &key.to_string())
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?; // No need to delete
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn handle_delete_internal(
|
||||
let version_uuid = gen_uuid();
|
||||
|
||||
let object = Object::new(
|
||||
bucket.into(),
|
||||
bucket_id,
|
||||
key.into(),
|
||||
vec![ObjectVersion {
|
||||
uuid: version_uuid,
|
||||
@@ -61,11 +61,11 @@ async fn handle_delete_internal(
|
||||
|
||||
pub async fn handle_delete(
|
||||
garage: Arc<Garage>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let (_deleted_version, delete_marker_version) =
|
||||
handle_delete_internal(&garage, bucket, key).await?;
|
||||
handle_delete_internal(&garage, bucket_id, key).await?;
|
||||
|
||||
Ok(Response::builder()
|
||||
.header("x-amz-version-id", hex::encode(delete_marker_version))
|
||||
@@ -76,7 +76,7 @@ pub async fn handle_delete(
|
||||
|
||||
pub async fn handle_delete_objects(
|
||||
garage: Arc<Garage>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
req: Request<Body>,
|
||||
content_sha256: Option<Hash>,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
@@ -90,7 +90,7 @@ pub async fn handle_delete_objects(
|
||||
let mut ret_errors = Vec::new();
|
||||
|
||||
for obj in cmd.objects.iter() {
|
||||
match handle_delete_internal(&garage, bucket, &obj.key).await {
|
||||
match handle_delete_internal(&garage, bucket_id, &obj.key).await {
|
||||
Ok((deleted_version, delete_marker_version)) => {
|
||||
if cmd.quiet {
|
||||
continue;
|
||||
|
||||
+5
-4
@@ -7,6 +7,7 @@ use hyper::body::Bytes;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
|
||||
use garage_table::EmptyKey;
|
||||
use garage_util::data::*;
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::object_table::*;
|
||||
@@ -84,12 +85,12 @@ fn try_answer_cached(
|
||||
pub async fn handle_head(
|
||||
garage: Arc<Garage>,
|
||||
req: &Request<Body>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let object = garage
|
||||
.object_table
|
||||
.get(&bucket.to_string(), &key.to_string())
|
||||
.get(&bucket_id, &key.to_string())
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
|
||||
@@ -123,12 +124,12 @@ pub async fn handle_head(
|
||||
pub async fn handle_get(
|
||||
garage: Arc<Garage>,
|
||||
req: &Request<Body>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let object = garage
|
||||
.object_table
|
||||
.get(&bucket.to_string(), &key.to_string())
|
||||
.get(&bucket_id, &key.to_string())
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
|
||||
|
||||
+5
-3
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use hyper::{Body, Response};
|
||||
|
||||
use garage_util::data::*;
|
||||
use garage_util::error::Error as GarageError;
|
||||
use garage_util::time::*;
|
||||
|
||||
@@ -18,7 +19,8 @@ use crate::s3_xml;
|
||||
#[derive(Debug)]
|
||||
pub struct ListObjectsQuery {
|
||||
pub is_v2: bool,
|
||||
pub bucket: String,
|
||||
pub bucket_name: String,
|
||||
pub bucket_id: Uuid,
|
||||
pub delimiter: Option<String>,
|
||||
pub max_keys: usize,
|
||||
pub prefix: String,
|
||||
@@ -102,7 +104,7 @@ pub async fn handle_list(
|
||||
let objects = garage
|
||||
.object_table
|
||||
.get_range(
|
||||
&query.bucket,
|
||||
&query.bucket_id,
|
||||
Some(next_chunk_start.clone()),
|
||||
Some(DeletedFilter::NotDeleted),
|
||||
query.max_keys + 1,
|
||||
@@ -232,7 +234,7 @@ pub async fn handle_list(
|
||||
|
||||
let mut result = s3_xml::ListBucketResult {
|
||||
xmlns: (),
|
||||
name: s3_xml::Value(query.bucket.to_string()),
|
||||
name: s3_xml::Value(query.bucket_name.to_string()),
|
||||
prefix: uriencode_maybe(&query.prefix, query.urlencode_resp),
|
||||
marker: None,
|
||||
next_marker: None,
|
||||
|
||||
+22
-22
@@ -24,7 +24,7 @@ use crate::signature::verify_signed_content;
|
||||
pub async fn handle_put(
|
||||
garage: Arc<Garage>,
|
||||
req: Request<Body>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
content_sha256: Option<Hash>,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
@@ -77,7 +77,7 @@ pub async fn handle_put(
|
||||
)),
|
||||
};
|
||||
|
||||
let object = Object::new(bucket.into(), key.into(), vec![object_version]);
|
||||
let object = Object::new(bucket_id, key.into(), vec![object_version]);
|
||||
garage.object_table.insert(&object).await?;
|
||||
|
||||
return Ok(put_response(version_uuid, data_md5sum_hex));
|
||||
@@ -90,14 +90,14 @@ pub async fn handle_put(
|
||||
timestamp: version_timestamp,
|
||||
state: ObjectVersionState::Uploading(headers.clone()),
|
||||
};
|
||||
let object = Object::new(bucket.into(), key.into(), vec![object_version.clone()]);
|
||||
let object = Object::new(bucket_id, key.into(), vec![object_version.clone()]);
|
||||
garage.object_table.insert(&object).await?;
|
||||
|
||||
// Initialize corresponding entry in version table
|
||||
// Write this entry now, even with empty block list,
|
||||
// to prevent block_ref entries from being deleted (they can be deleted
|
||||
// if the reference a version that isn't found in the version table)
|
||||
let version = Version::new(version_uuid, bucket.into(), key.into(), false);
|
||||
let version = Version::new(version_uuid, bucket_id, key.into(), false);
|
||||
garage.version_table.insert(&version).await?;
|
||||
|
||||
// Transfer data and verify checksum
|
||||
@@ -127,7 +127,7 @@ pub async fn handle_put(
|
||||
Err(e) => {
|
||||
// Mark object as aborted, this will free the blocks further down
|
||||
object_version.state = ObjectVersionState::Aborted;
|
||||
let object = Object::new(bucket.into(), key.into(), vec![object_version.clone()]);
|
||||
let object = Object::new(bucket_id, key.into(), vec![object_version.clone()]);
|
||||
garage.object_table.insert(&object).await?;
|
||||
return Err(e);
|
||||
}
|
||||
@@ -143,7 +143,7 @@ pub async fn handle_put(
|
||||
},
|
||||
first_block_hash,
|
||||
));
|
||||
let object = Object::new(bucket.into(), key.into(), vec![object_version]);
|
||||
let object = Object::new(bucket_id, key.into(), vec![object_version]);
|
||||
garage.object_table.insert(&object).await?;
|
||||
|
||||
Ok(put_response(version_uuid, md5sum_hex))
|
||||
@@ -315,7 +315,8 @@ pub fn put_response(version_uuid: Uuid, md5sum_hex: String) -> Response<Body> {
|
||||
pub async fn handle_create_multipart_upload(
|
||||
garage: Arc<Garage>,
|
||||
req: &Request<Body>,
|
||||
bucket: &str,
|
||||
bucket_name: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let version_uuid = gen_uuid();
|
||||
@@ -327,20 +328,20 @@ pub async fn handle_create_multipart_upload(
|
||||
timestamp: now_msec(),
|
||||
state: ObjectVersionState::Uploading(headers),
|
||||
};
|
||||
let object = Object::new(bucket.to_string(), key.to_string(), vec![object_version]);
|
||||
let object = Object::new(bucket_id, key.to_string(), vec![object_version]);
|
||||
garage.object_table.insert(&object).await?;
|
||||
|
||||
// Insert empty version so that block_ref entries refer to something
|
||||
// (they are inserted concurrently with blocks in the version table, so
|
||||
// there is the possibility that they are inserted before the version table
|
||||
// is created, in which case it is allowed to delete them, e.g. in repair_*)
|
||||
let version = Version::new(version_uuid, bucket.into(), key.into(), false);
|
||||
let version = Version::new(version_uuid, bucket_id, key.into(), false);
|
||||
garage.version_table.insert(&version).await?;
|
||||
|
||||
// Send success response
|
||||
let result = s3_xml::InitiateMultipartUploadResult {
|
||||
xmlns: (),
|
||||
bucket: s3_xml::Value(bucket.to_string()),
|
||||
bucket: s3_xml::Value(bucket_name.to_string()),
|
||||
key: s3_xml::Value(key.to_string()),
|
||||
upload_id: s3_xml::Value(hex::encode(version_uuid)),
|
||||
};
|
||||
@@ -352,7 +353,7 @@ pub async fn handle_create_multipart_upload(
|
||||
pub async fn handle_put_part(
|
||||
garage: Arc<Garage>,
|
||||
req: Request<Body>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
part_number: u64,
|
||||
upload_id: &str,
|
||||
@@ -366,12 +367,11 @@ pub async fn handle_put_part(
|
||||
};
|
||||
|
||||
// Read first chuck, and at the same time try to get object to see if it exists
|
||||
let bucket = bucket.to_string();
|
||||
let key = key.to_string();
|
||||
let mut chunker = BodyChunker::new(req.into_body(), garage.config.block_size);
|
||||
|
||||
let (object, first_block) =
|
||||
futures::try_join!(garage.object_table.get(&bucket, &key), chunker.next(),)?;
|
||||
futures::try_join!(garage.object_table.get(&bucket_id, &key), chunker.next(),)?;
|
||||
|
||||
// Check object is valid and multipart block can be accepted
|
||||
let first_block = first_block.ok_or_else(|| Error::BadRequest("Empty body".to_string()))?;
|
||||
@@ -386,7 +386,7 @@ pub async fn handle_put_part(
|
||||
}
|
||||
|
||||
// Copy block to store
|
||||
let version = Version::new(version_uuid, bucket, key, false);
|
||||
let version = Version::new(version_uuid, bucket_id, key, false);
|
||||
let first_block_hash = blake2sum(&first_block[..]);
|
||||
let (_, data_md5sum, data_sha256sum) = read_and_put_blocks(
|
||||
&garage,
|
||||
@@ -424,7 +424,8 @@ pub async fn handle_put_part(
|
||||
pub async fn handle_complete_multipart_upload(
|
||||
garage: Arc<Garage>,
|
||||
req: Request<Body>,
|
||||
bucket: &str,
|
||||
bucket_name: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
upload_id: &str,
|
||||
content_sha256: Option<Hash>,
|
||||
@@ -442,10 +443,9 @@ pub async fn handle_complete_multipart_upload(
|
||||
|
||||
let version_uuid = decode_upload_id(upload_id)?;
|
||||
|
||||
let bucket = bucket.to_string();
|
||||
let key = key.to_string();
|
||||
let (object, version) = futures::try_join!(
|
||||
garage.object_table.get(&bucket, &key),
|
||||
garage.object_table.get(&bucket_id, &key),
|
||||
garage.version_table.get(&version_uuid, &EmptyKey),
|
||||
)?;
|
||||
|
||||
@@ -510,14 +510,14 @@ pub async fn handle_complete_multipart_upload(
|
||||
version.blocks.items()[0].1.hash,
|
||||
));
|
||||
|
||||
let final_object = Object::new(bucket.clone(), key.clone(), vec![object_version]);
|
||||
let final_object = Object::new(bucket_id, key.clone(), vec![object_version]);
|
||||
garage.object_table.insert(&final_object).await?;
|
||||
|
||||
// Send response saying ok we're done
|
||||
let result = s3_xml::CompleteMultipartUploadResult {
|
||||
xmlns: (),
|
||||
location: None,
|
||||
bucket: s3_xml::Value(bucket),
|
||||
bucket: s3_xml::Value(bucket_name.to_string()),
|
||||
key: s3_xml::Value(key),
|
||||
etag: s3_xml::Value(etag),
|
||||
};
|
||||
@@ -528,7 +528,7 @@ pub async fn handle_complete_multipart_upload(
|
||||
|
||||
pub async fn handle_abort_multipart_upload(
|
||||
garage: Arc<Garage>,
|
||||
bucket: &str,
|
||||
bucket_id: Uuid,
|
||||
key: &str,
|
||||
upload_id: &str,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
@@ -536,7 +536,7 @@ pub async fn handle_abort_multipart_upload(
|
||||
|
||||
let object = garage
|
||||
.object_table
|
||||
.get(&bucket.to_string(), &key.to_string())
|
||||
.get(&bucket_id, &key.to_string())
|
||||
.await?;
|
||||
let object = object.ok_or_else(|| Error::BadRequest("Object not found".to_string()))?;
|
||||
|
||||
@@ -550,7 +550,7 @@ pub async fn handle_abort_multipart_upload(
|
||||
};
|
||||
|
||||
object_version.state = ObjectVersionState::Aborted;
|
||||
let final_object = Object::new(bucket.to_string(), key.to_string(), vec![object_version]);
|
||||
let final_object = Object::new(bucket_id, key.to_string(), vec![object_version]);
|
||||
garage.object_table.insert(&final_object).await?;
|
||||
|
||||
Ok(Response::new(Body::from(vec![])))
|
||||
|
||||
+18
-9
@@ -7,9 +7,10 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::error::*;
|
||||
use crate::s3_xml::{xmlns_tag, IntValue, Value};
|
||||
use crate::signature::verify_signed_content;
|
||||
use garage_model::bucket_table::BucketState;
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
use garage_table::*;
|
||||
use garage_util::crdt;
|
||||
use garage_util::data::Hash;
|
||||
|
||||
pub async fn handle_delete_website(
|
||||
@@ -17,14 +18,18 @@ pub async fn handle_delete_website(
|
||||
bucket: String,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let mut bucket = garage
|
||||
.bucket_table
|
||||
.bucket_alias_table
|
||||
.get(&EmptyKey, &bucket)
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
|
||||
if let BucketState::Present(state) = bucket.state.get_mut() {
|
||||
state.website.update(false);
|
||||
garage.bucket_table.insert(&bucket).await?;
|
||||
if let crdt::Deletable::Present(state) = bucket.state.get_mut() {
|
||||
let mut new_param = state.clone();
|
||||
new_param.website_access = false;
|
||||
bucket.state.update(crdt::Deletable::present(new_param));
|
||||
garage.bucket_alias_table.insert(&bucket).await?;
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
Ok(Response::builder()
|
||||
@@ -43,7 +48,7 @@ pub async fn handle_put_website(
|
||||
verify_signed_content(content_sha256, &body[..])?;
|
||||
|
||||
let mut bucket = garage
|
||||
.bucket_table
|
||||
.bucket_alias_table
|
||||
.get(&EmptyKey, &bucket)
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
@@ -51,9 +56,13 @@ pub async fn handle_put_website(
|
||||
let conf: WebsiteConfiguration = from_reader(&body as &[u8])?;
|
||||
conf.validate()?;
|
||||
|
||||
if let BucketState::Present(state) = bucket.state.get_mut() {
|
||||
state.website.update(true);
|
||||
garage.bucket_table.insert(&bucket).await?;
|
||||
if let crdt::Deletable::Present(state) = bucket.state.get() {
|
||||
let mut new_param = state.clone();
|
||||
new_param.website_access = true;
|
||||
bucket.state.update(crdt::Deletable::present(new_param));
|
||||
garage.bucket_alias_table.insert(&bucket).await?;
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
Ok(Response::builder()
|
||||
|
||||
@@ -64,7 +64,7 @@ pub async fn check_signature(
|
||||
.key_table
|
||||
.get(&EmptyKey, &authorization.key_id)
|
||||
.await?
|
||||
.filter(|k| !k.deleted.get())
|
||||
.filter(|k| !k.state.is_deleted())
|
||||
.ok_or_else(|| Error::Forbidden(format!("No such key: {}", authorization.key_id)))?;
|
||||
|
||||
let canonical_request = canonical_request(
|
||||
|
||||
Reference in New Issue
Block a user