Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(services/gcs): Allow setting default storage_class #1996

Merged
merged 3 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions core/src/services/gcs/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const DEFAULT_GCS_SCOPE: &str = "https://www.googleapis.com/auth/devstorage.read
/// - `endpoint`: Customizable endpoint setting
/// - `credentials`: Credential string for GCS OAuth2
/// - `predefined_acl`: Predefined ACL for GCS
/// - `default_storage_class`: Default storage class for GCS
///
/// You can refer to [`GcsBuilder`]'s docs for more information
///
Expand All @@ -88,6 +89,8 @@ const DEFAULT_GCS_SCOPE: &str = "https://www.googleapis.com/auth/devstorage.read
/// builder.credential("authentication token");
/// // set the predefined ACL for GCS
/// builder.predefined_acl("publicRead");
/// // set the default storage class for GCS
/// builder.default_storage_class("STANDARD");
///
/// let op: Operator = Operator::new(builder)?.finish();
/// Ok(())
Expand Down Expand Up @@ -115,6 +118,7 @@ pub struct GcsBuilder {
http_client: Option<HttpClient>,
customed_token_loader: Option<Box<dyn GoogleTokenLoad>>,
predefined_acl: Option<String>,
default_storage_class: String,
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
}

impl GcsBuilder {
Expand Down Expand Up @@ -218,6 +222,20 @@ impl GcsBuilder {
};
self
}

/// Set the default storage class for GCS.
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
///
/// Available values are:
/// - `STANDARD`
/// - `NEARLINE`
/// - `COLDLINE`
/// - `ARCHIVE`
pub fn default_storage_class(&mut self, class: &str) -> &mut Self {
if !class.is_empty() {
self.default_storage_class = class.to_string()
};
self
}
}

impl Debug for GcsBuilder {
Expand All @@ -233,6 +251,7 @@ impl Debug for GcsBuilder {
if self.predefined_acl.is_some() {
ds.field("predefined_acl", &self.predefined_acl);
}
ds.field("default_storage_class", &self.default_storage_class);
ds.finish()
}
}
Expand All @@ -250,6 +269,8 @@ impl Builder for GcsBuilder {
map.get("credential").map(|v| builder.credential(v));
map.get("scope").map(|v| builder.scope(v));
map.get("predefined_acl").map(|v| builder.predefined_acl(v));
map.get("default_storage_class")
.map(|v| builder.default_storage_class(v));

builder
}
Expand Down Expand Up @@ -314,6 +335,10 @@ impl Builder for GcsBuilder {

let signer = GoogleSigner::new("storage");

if self.default_storage_class.is_empty() {
self.default_storage_class = "STANDARD".to_string();
}

let backend = GcsBackend {
core: Arc::new(GcsCore {
endpoint,
Expand All @@ -324,6 +349,7 @@ impl Builder for GcsBuilder {
token_loader,
credential_loader: cred_loader,
predefined_acl: self.predefined_acl.clone(),
default_storage_class: self.default_storage_class.clone(),
}),
};

Expand Down
35 changes: 27 additions & 8 deletions core/src/services/gcs/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use std::fmt::Write;

use backon::ExponentialBuilder;
use backon::Retryable;
use bytes::BytesMut;
use http::header::CONTENT_LENGTH;
use http::header::CONTENT_TYPE;
use http::Request;
Expand All @@ -46,15 +47,20 @@ pub struct GcsCore {
pub credential_loader: GoogleCredentialLoader,

pub predefined_acl: Option<String>,
pub default_storage_class: String,
}

impl Debug for GcsCore {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut de = f.debug_struct("Backend");
de.field("endpoint", &self.endpoint)
.field("bucket", &self.bucket)
.field("root", &self.root)
.finish_non_exhaustive()
.field("root", &self.root);
if self.predefined_acl.is_some() {
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
de.field("predefined_acl", &self.predefined_acl);
}
de.field("default_storage_class", &self.default_storage_class);
de.finish_non_exhaustive()
}
}

Expand Down Expand Up @@ -139,7 +145,7 @@ impl GcsCore {
let p = build_abs_path(&self.root, path);

let mut url = format!(
"{}/upload/storage/v1/b/{}/o?uploadType=media&name={}",
"{}/upload/storage/v1/b/{}/o?uploadType=multipart&name={}",
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
self.endpoint,
self.bucket,
percent_encode_path(&p)
Expand All @@ -149,19 +155,32 @@ impl GcsCore {
write!(&mut url, "&predefinedAcl={}", acl).unwrap();
}

let mut req = Request::post(&url);
let mut req =
Request::post(&url).header(CONTENT_TYPE, "multipart/related; boundary=my-boundary");
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved

if let Some(size) = size {
req = req.header(CONTENT_LENGTH, size)
}

let mut req_body = BytesMut::with_capacity(100);
write!(
&mut req_body,
"--my-boundary\nContent-Type: application/json; charset=UTF-8\n\n{{\"storageClass\": \"{}\"}}\n\n--my-boundary\n",
self.default_storage_class
).unwrap();

if let Some(mime) = content_type {
req = req.header(CONTENT_TYPE, mime)
write!(&mut req_body, "Content-Type: {}\n\n", mime).unwrap();
} else {
write!(&mut req_body, "Content-Type: application/octet-stream\n\n").unwrap();
}

// Set body
let req = req.body(body).map_err(new_request_build_error)?;

if let AsyncBody::Bytes(bytes) = body {
req_body.extend_from_slice(&bytes);
}
write!(&mut req_body, "\n--my-boundary").unwrap();
let req_body = AsyncBody::Bytes(req_body.freeze());
let req = req.body(req_body).map_err(new_request_build_error)?;
Ok(req)
}

Expand Down