1use std::fmt::Debug;
19use std::sync::Arc;
20
21use log::debug;
22use reqsign_core::Context;
23use reqsign_core::Env as _;
24use reqsign_core::OsEnv;
25use reqsign_core::ProvideCredential;
26use reqsign_core::ProvideCredentialChain;
27use reqsign_core::Signer;
28use reqsign_core::StaticEnv;
29use reqsign_file_read_tokio::TokioFileRead;
30use reqsign_google::Credential;
31use reqsign_google::DefaultCredentialProvider;
32use reqsign_google::FileCredentialProvider;
33use reqsign_google::RequestSigner;
34use reqsign_google::StaticCredentialProvider;
35use reqsign_google::TokenCredentialProvider;
36use reqsign_google::VmMetadataCredentialProvider;
37
38use super::GCS_SCHEME;
39use super::config::GcsConfig;
40use super::copier::GcsCopier;
41use super::core::constants::GCS_REWRITE_MAX_CHUNK_SIZE;
42use super::core::constants::GCS_REWRITE_MIN_CHUNK_SIZE;
43use super::core::parse_error;
44use super::core::*;
45use super::deleter::GcsDeleter;
46use super::lister::GcsLister;
47use super::reader::*;
48use super::writer::GcsWriter;
49use super::writer::GcsWriters;
50use opendal_core::raw::*;
51use opendal_core::*;
52
53const DEFAULT_GCS_ENDPOINT: &str = "https://storage.googleapis.com";
54const DEFAULT_GCS_SCOPE: &str = "https://www.googleapis.com/auth/devstorage.read_write";
55
56#[doc = include_str!("docs.md")]
58#[derive(Default)]
59pub struct GcsBuilder {
60 pub(super) config: GcsConfig,
61 pub(super) credential_provider_chain: Option<ProvideCredentialChain<Credential>>,
62}
63
64impl Debug for GcsBuilder {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("GcsBuilder")
67 .field("config", &self.config)
68 .finish_non_exhaustive()
69 }
70}
71
72impl GcsBuilder {
73 pub fn root(mut self, root: &str) -> Self {
75 self.config.root = if root.is_empty() {
76 None
77 } else {
78 Some(root.to_string())
79 };
80
81 self
82 }
83
84 pub fn bucket(mut self, bucket: &str) -> Self {
86 self.config.bucket = bucket.to_string();
87 self
88 }
89
90 pub fn scope(mut self, scope: &str) -> Self {
102 if !scope.is_empty() {
103 self.config.scope = Some(scope.to_string())
104 };
105 self
106 }
107
108 pub fn service_account(mut self, service_account: &str) -> Self {
113 if !service_account.is_empty() {
114 self.config.service_account = Some(service_account.to_string())
115 };
116 self
117 }
118
119 pub fn endpoint(mut self, endpoint: &str) -> Self {
121 if !endpoint.is_empty() {
122 self.config.endpoint = Some(endpoint.to_string())
123 };
124 self
125 }
126
127 pub fn credential(mut self, credential: &str) -> Self {
135 if !credential.is_empty() {
136 self.config.credential = Some(credential.to_string())
137 };
138 self
139 }
140
141 pub fn credential_path(mut self, path: &str) -> Self {
148 if !path.is_empty() {
149 self.config.credential_path = Some(path.to_string())
150 };
151 self
152 }
153
154 pub fn credential_provider(
158 mut self,
159 provider: impl ProvideCredential<Credential = Credential> + 'static,
160 ) -> Self {
161 let chain = self.credential_provider_chain.unwrap_or_default();
162 self.credential_provider_chain = Some(chain.push_front(provider));
163 self
164 }
165
166 pub fn credential_provider_chain(mut self, chain: ProvideCredentialChain<Credential>) -> Self {
170 self.credential_provider_chain = Some(chain);
171 self
172 }
173
174 pub fn token(mut self, token: String) -> Self {
176 self.config.token = Some(token);
177 self
178 }
179
180 pub fn disable_vm_metadata(mut self) -> Self {
182 self.config.disable_vm_metadata = true;
183 self
184 }
185
186 pub fn disable_config_load(mut self) -> Self {
188 self.config.disable_config_load = true;
189 self
190 }
191
192 pub fn predefined_acl(mut self, acl: &str) -> Self {
202 if !acl.is_empty() {
203 self.config.predefined_acl = Some(acl.to_string())
204 };
205 self
206 }
207
208 pub fn default_storage_class(mut self, class: &str) -> Self {
216 if !class.is_empty() {
217 self.config.default_storage_class = Some(class.to_string())
218 };
219 self
220 }
221
222 pub fn skip_signature(mut self) -> Self {
227 self.config.skip_signature = true;
228 self
229 }
230
231 #[deprecated(
233 since = "0.57.0",
234 note = "Please use `skip_signature` instead of `allow_anonymous`"
235 )]
236 pub fn allow_anonymous(self) -> Self {
237 self.skip_signature()
238 }
239}
240
241impl Builder for GcsBuilder {
242 type Config = GcsConfig;
243
244 fn build(self) -> Result<impl Service> {
245 debug!("backend build started: {self:?}");
246
247 #[allow(deprecated)]
248 let skip_signature = self.config.skip_signature || self.config.allow_anonymous;
249
250 let root = normalize_root(&self.config.root.unwrap_or_default());
251 debug!("backend use root {root}");
252
253 let bucket = match self.config.bucket.is_empty() {
255 false => Ok(&self.config.bucket),
256 true => Err(
257 Error::new(ErrorKind::ConfigInvalid, "The bucket is misconfigured")
258 .with_operation("Builder::build")
259 .with_context("service", GCS_SCHEME),
260 ),
261 }?;
262
263 let endpoint = self
266 .config
267 .endpoint
268 .clone()
269 .unwrap_or_else(|| DEFAULT_GCS_ENDPOINT.to_string());
270 debug!("backend use endpoint: {endpoint}");
271
272 let scope = self
273 .config
274 .scope
275 .clone()
276 .unwrap_or_else(|| DEFAULT_GCS_SCOPE.to_string());
277
278 let os_env = OsEnv;
279 let mut envs = os_env.vars();
280 envs.insert("GOOGLE_SCOPE".to_string(), scope.clone());
281
282 let ctx = Context::new()
283 .with_file_read(TokioFileRead)
284 .with_env(StaticEnv {
285 home_dir: os_env.home_dir(),
286 envs,
287 });
288
289 let mut default_credential = DefaultCredentialProvider::builder();
290 #[cfg(target_arch = "wasm32")]
291 {
292 default_credential = default_credential.no_env().no_well_known();
293 }
294
295 if self.config.disable_config_load {
296 default_credential = default_credential.no_env().no_well_known();
297 }
298
299 if self.config.disable_vm_metadata || self.config.service_account.is_some() {
300 default_credential = default_credential.no_vm_metadata();
301 }
302
303 let mut credential_chain = ProvideCredentialChain::new().push(default_credential.build());
304
305 if !self.config.disable_vm_metadata
306 && let Some(service_account) = self.config.service_account.as_deref()
307 {
308 credential_chain = credential_chain.push(
309 VmMetadataCredentialProvider::new()
310 .with_scope(&scope)
311 .with_service_account(service_account),
312 );
313 }
314
315 if let Some(path) = self.config.credential_path.as_deref() {
316 credential_chain =
317 credential_chain.push_front(FileCredentialProvider::new(path).with_scope(&scope));
318 }
319
320 if let Some(content) = self.config.credential.as_deref()
321 && let Ok(provider) = StaticCredentialProvider::from_base64(content)
322 {
323 credential_chain = credential_chain.push_front(provider.with_scope(&scope));
324 }
325
326 if let Some(token) = self.config.token.as_deref() {
327 credential_chain = credential_chain.push_front(TokenCredentialProvider::new(token));
328 }
329
330 if let Some(customized_credential_chain) = self.credential_provider_chain {
331 credential_chain = credential_chain.push_front(customized_credential_chain);
332 }
333
334 let sign_ctx = ctx;
335 let signer = Signer::new(
336 sign_ctx.clone(),
337 credential_chain,
338 RequestSigner::new("storage").with_scope(&scope),
339 );
340
341 let info = ServiceInfo::new(GCS_SCHEME, &root, bucket);
342 let capability = Capability {
343 stat: true,
344 stat_with_if_match: true,
345 stat_with_if_none_match: true,
346
347 read: true,
348 read_with_suffix: true,
349
350 read_with_if_match: true,
351 read_with_if_none_match: true,
352
353 write: true,
354 write_can_empty: true,
355 write_can_multi: true,
356 write_with_cache_control: true,
357 write_with_content_type: true,
358 write_with_content_encoding: true,
359 write_with_user_metadata: true,
360 write_with_if_not_exists: true,
361
362 write_multi_min_size: Some(5 * 1024 * 1024),
366 write_multi_max_size: if cfg!(target_pointer_width = "64") {
370 Some(5 * 1024 * 1024 * 1024)
371 } else {
372 Some(usize::MAX)
373 },
374
375 delete: true,
376 delete_max_size: Some(100),
377
378 copy: true,
379 copy_can_multi: true,
380 copy_multi_min_size: Some(GCS_REWRITE_MIN_CHUNK_SIZE),
385 copy_multi_max_size: Some(GCS_REWRITE_MAX_CHUNK_SIZE),
386
387 list: true,
388 list_with_limit: true,
389 list_with_start_after: true,
390 list_with_recursive: true,
391
392 presign: true,
393 presign_stat: true,
394 presign_read: true,
395 presign_write: true,
396
397 shared: true,
398
399 ..Default::default()
400 };
401
402 let backend = GcsBackend {
403 core: Arc::new(GcsCore {
404 info,
405 capability,
406 endpoint,
407 bucket: bucket.to_string(),
408 root,
409 signer,
410 sign_ctx,
411 predefined_acl: self.config.predefined_acl.clone(),
412 default_storage_class: self.config.default_storage_class.clone(),
413 skip_signature,
414 }),
415 };
416
417 Ok(backend)
418 }
419}
420
421#[derive(Clone, Debug)]
423pub struct GcsBackend {
424 pub(crate) core: Arc<GcsCore>,
425}
426
427impl Service for GcsBackend {
428 type Reader = oio::StreamReader<GcsReader>;
429 type Writer = GcsWriters;
430 type Lister = oio::PageLister<GcsLister>;
431 type Deleter = oio::BatchDeleter<GcsDeleter>;
432 type Copier = GcsCopier;
433
434 fn info(&self) -> ServiceInfo {
435 self.core.info.clone()
436 }
437
438 fn capability(&self) -> Capability {
439 self.core.capability
440 }
441
442 async fn create_dir(
443 &self,
444 _ctx: &OperationContext,
445 _path: &str,
446 _args: OpCreateDir,
447 ) -> Result<RpCreateDir> {
448 Err(Error::new(
449 ErrorKind::Unsupported,
450 "operation is not supported",
451 ))
452 }
453
454 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
455 let resp = self.core.gcs_get_object_metadata(ctx, path, &args).await?;
456
457 if !resp.status().is_success() {
458 return Err(parse_error(resp));
459 }
460
461 let slc = resp.into_body();
462 let m = GcsCore::build_metadata_from_object_response(path, slc)?;
463
464 Ok(RpStat::new(m))
465 }
466 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
467 let output: oio::StreamReader<GcsReader> = {
468 Ok(oio::StreamReader::new(GcsReader::new(
469 self.clone(),
470 ctx.clone(),
471 path,
472 args,
473 )))
474 }?;
475
476 Ok(output)
477 }
478
479 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
480 let output: GcsWriters = {
481 let concurrent = args.concurrent();
482 let w = GcsWriter::new(self.core.clone(), ctx.clone(), path, args);
483 let w = oio::MultipartWriter::new(ctx.executor().clone(), w, concurrent);
486
487 Ok(w)
488 }?;
489
490 Ok(output)
491 }
492
493 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
494 let output: oio::BatchDeleter<GcsDeleter> = {
495 Ok(oio::BatchDeleter::new(
496 GcsDeleter::new(self.core.clone(), ctx.clone()),
497 self.core.capability.delete_max_size,
498 ))
499 }?;
500
501 Ok(output)
502 }
503
504 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
505 let output: oio::PageLister<GcsLister> = {
506 let l = GcsLister::new(
507 self.core.clone(),
508 ctx.clone(),
509 path,
510 args.recursive(),
511 args.limit(),
512 args.start_after(),
513 );
514
515 Ok(oio::PageLister::new(l))
516 }?;
517
518 Ok(output)
519 }
520
521 fn copy(
522 &self,
523 ctx: &OperationContext,
524 from: &str,
525 to: &str,
526 args: OpCopy,
527 opts: OpCopier,
528 ) -> Result<Self::Copier> {
529 let output: GcsCopier = {
530 let copier = GcsCopier::new(self.core.clone(), ctx.clone(), from, to, args, opts);
531 Ok(copier)
532 }?;
533
534 Ok(output)
535 }
536
537 async fn rename(
538 &self,
539 _ctx: &OperationContext,
540 _from: &str,
541 _to: &str,
542 _args: OpRename,
543 ) -> Result<RpRename> {
544 Err(Error::new(
545 ErrorKind::Unsupported,
546 "operation is not supported",
547 ))
548 }
549
550 async fn presign(
551 &self,
552 ctx: &OperationContext,
553 path: &str,
554 args: OpPresign,
555 ) -> Result<RpPresign> {
556 let req = match args.operation() {
558 PresignOperation::Stat(v) => self.core.gcs_head_object_xml_request(path, v),
559 PresignOperation::Read(range, v) => {
560 self.core.gcs_get_object_xml_request(path, *range, v)
561 }
562 PresignOperation::Write(v) => {
563 self.core
564 .gcs_insert_object_xml_request(path, v, Buffer::new())
565 }
566 PresignOperation::Delete(_) => Err(Error::new(
567 ErrorKind::Unsupported,
568 "operation is not supported",
569 )),
570 _ => Err(Error::new(
571 ErrorKind::Unsupported,
572 "operation is not supported",
573 )),
574 };
575 let req = req?;
576 let req = self.core.sign_query(ctx, req, args.expire()).await?;
577
578 let (parts, _) = req.into_parts();
580
581 Ok(RpPresign::new(PresignedRequest::new(
582 parts.method,
583 parts.uri,
584 parts.headers,
585 )))
586 }
587}