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::composer::GcsComposer;
40use super::config::GcsConfig;
41use super::copier::GcsCopier;
42use super::core::constants::GCS_REWRITE_MAX_CHUNK_SIZE;
43use super::core::constants::GCS_REWRITE_MIN_CHUNK_SIZE;
44use super::core::parse_error;
45use super::core::*;
46use super::deleter::GcsDeleter;
47use super::lister::GcsLister;
48use super::reader::*;
49use super::writer::GcsConditionalWriter;
50use super::writer::GcsWriter;
51use super::writer::GcsWriters;
52use opendal_core::raw::*;
53use opendal_core::*;
54
55const DEFAULT_GCS_ENDPOINT: &str = "https://storage.googleapis.com";
56const DEFAULT_GCS_SCOPE: &str = "https://www.googleapis.com/auth/devstorage.read_write";
57
58#[doc = include_str!("docs.md")]
60#[derive(Default)]
61pub struct GcsBuilder {
62 pub(super) config: GcsConfig,
63 pub(super) credential_provider_chain: Option<ProvideCredentialChain<Credential>>,
64}
65
66impl Debug for GcsBuilder {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("GcsBuilder")
69 .field("config", &self.config)
70 .finish_non_exhaustive()
71 }
72}
73
74impl GcsBuilder {
75 pub fn root(mut self, root: &str) -> Self {
77 self.config.root = if root.is_empty() {
78 None
79 } else {
80 Some(root.to_string())
81 };
82
83 self
84 }
85
86 pub fn bucket(mut self, bucket: &str) -> Self {
88 self.config.bucket = bucket.to_string();
89 self
90 }
91
92 pub fn scope(mut self, scope: &str) -> Self {
104 if !scope.is_empty() {
105 self.config.scope = Some(scope.to_string())
106 };
107 self
108 }
109
110 pub fn service_account(mut self, service_account: &str) -> Self {
115 if !service_account.is_empty() {
116 self.config.service_account = Some(service_account.to_string())
117 };
118 self
119 }
120
121 pub fn endpoint(mut self, endpoint: &str) -> Self {
123 if !endpoint.is_empty() {
124 self.config.endpoint = Some(endpoint.to_string())
125 };
126 self
127 }
128
129 pub fn credential(mut self, credential: &str) -> Self {
137 if !credential.is_empty() {
138 self.config.credential = Some(credential.to_string())
139 };
140 self
141 }
142
143 pub fn credential_path(mut self, path: &str) -> Self {
150 if !path.is_empty() {
151 self.config.credential_path = Some(path.to_string())
152 };
153 self
154 }
155
156 pub fn credential_provider(
160 mut self,
161 provider: impl ProvideCredential<Credential = Credential> + 'static,
162 ) -> Self {
163 let chain = self.credential_provider_chain.unwrap_or_default();
164 self.credential_provider_chain = Some(chain.push_front(provider));
165 self
166 }
167
168 pub fn credential_provider_chain(mut self, chain: ProvideCredentialChain<Credential>) -> Self {
172 self.credential_provider_chain = Some(chain);
173 self
174 }
175
176 pub fn token(mut self, token: String) -> Self {
178 self.config.token = Some(token);
179 self
180 }
181
182 pub fn disable_vm_metadata(mut self) -> Self {
184 self.config.disable_vm_metadata = true;
185 self
186 }
187
188 pub fn disable_config_load(mut self) -> Self {
190 self.config.disable_config_load = true;
191 self
192 }
193
194 pub fn predefined_acl(mut self, acl: &str) -> Self {
204 if !acl.is_empty() {
205 self.config.predefined_acl = Some(acl.to_string())
206 };
207 self
208 }
209
210 pub fn default_storage_class(mut self, class: &str) -> Self {
218 if !class.is_empty() {
219 self.config.default_storage_class = Some(class.to_string())
220 };
221 self
222 }
223
224 pub fn skip_signature(mut self) -> Self {
229 self.config.skip_signature = true;
230 self
231 }
232
233 #[deprecated(
235 since = "0.57.0",
236 note = "Please use `skip_signature` instead of `allow_anonymous`"
237 )]
238 pub fn allow_anonymous(self) -> Self {
239 self.skip_signature()
240 }
241}
242
243impl Builder for GcsBuilder {
244 type Config = GcsConfig;
245
246 fn build(self) -> Result<impl Service> {
247 debug!("backend build started: {self:?}");
248
249 #[allow(deprecated)]
250 let skip_signature = self.config.skip_signature || self.config.allow_anonymous;
251
252 let root = normalize_root(&self.config.root.unwrap_or_default());
253 debug!("backend use root {root}");
254
255 let bucket = match self.config.bucket.is_empty() {
257 false => Ok(&self.config.bucket),
258 true => Err(
259 Error::new(ErrorKind::ConfigInvalid, "The bucket is misconfigured")
260 .with_operation("Builder::build")
261 .with_context("service", GCS_SCHEME),
262 ),
263 }?;
264
265 let endpoint = self
268 .config
269 .endpoint
270 .clone()
271 .unwrap_or_else(|| DEFAULT_GCS_ENDPOINT.to_string());
272 debug!("backend use endpoint: {endpoint}");
273
274 let scope = self
275 .config
276 .scope
277 .clone()
278 .unwrap_or_else(|| DEFAULT_GCS_SCOPE.to_string());
279
280 let os_env = OsEnv;
281 let mut envs = os_env.vars();
282 envs.insert("GOOGLE_SCOPE".to_string(), scope.clone());
283
284 let ctx = Context::new()
285 .with_file_read(TokioFileRead)
286 .with_env(StaticEnv {
287 home_dir: os_env.home_dir(),
288 envs,
289 });
290
291 let mut default_credential = DefaultCredentialProvider::builder();
292 #[cfg(target_arch = "wasm32")]
293 {
294 default_credential = default_credential.no_env().no_well_known();
295 }
296
297 if self.config.disable_config_load {
298 default_credential = default_credential.no_env().no_well_known();
299 }
300
301 if self.config.disable_vm_metadata || self.config.service_account.is_some() {
302 default_credential = default_credential.no_vm_metadata();
303 }
304
305 let mut credential_chain = ProvideCredentialChain::new().push(default_credential.build());
306
307 if !self.config.disable_vm_metadata
308 && let Some(service_account) = self.config.service_account.as_deref()
309 {
310 credential_chain = credential_chain.push(
311 VmMetadataCredentialProvider::new()
312 .with_scope(&scope)
313 .with_service_account(service_account),
314 );
315 }
316
317 if let Some(path) = self.config.credential_path.as_deref() {
318 credential_chain =
319 credential_chain.push_front(FileCredentialProvider::new(path).with_scope(&scope));
320 }
321
322 if let Some(content) = self.config.credential.as_deref()
323 && let Ok(provider) = StaticCredentialProvider::from_base64(content)
324 {
325 credential_chain = credential_chain.push_front(provider.with_scope(&scope));
326 }
327
328 if let Some(token) = self.config.token.as_deref() {
329 credential_chain = credential_chain.push_front(TokenCredentialProvider::new(token));
330 }
331
332 if let Some(customized_credential_chain) = self.credential_provider_chain {
333 credential_chain = credential_chain.push_front(customized_credential_chain);
334 }
335
336 let sign_ctx = ctx;
337 let signer = Signer::new(
338 sign_ctx.clone(),
339 credential_chain,
340 RequestSigner::new("storage").with_scope(&scope),
341 );
342
343 let info = ServiceInfo::new(GCS_SCHEME, &root, bucket);
344 let capability = Capability {
345 stat: true,
346 stat_with_if_match: true,
347 stat_with_if_none_match: true,
348 stat_with_if_version_match: true,
349 stat_with_if_version_not_match: true,
350
351 read: true,
352 read_with_suffix: true,
353
354 read_with_if_match: true,
355 read_with_if_none_match: true,
356 read_with_if_version_match: true,
357 read_with_if_version_not_match: true,
358
359 write: true,
360 write_can_empty: true,
361 write_can_multi: true,
362 write_with_cache_control: true,
363 write_with_content_type: true,
364 write_with_content_encoding: true,
365 write_with_user_metadata: true,
366 write_with_if_not_exists: true,
367 write_with_if_version_match: true,
368 write_with_if_version_not_match: true,
369
370 write_multi_min_size: Some(5 * 1024 * 1024),
374 write_multi_max_size: if cfg!(target_pointer_width = "64") {
378 Some(5 * 1024 * 1024 * 1024)
379 } else {
380 Some(usize::MAX)
381 },
382
383 delete: true,
384 delete_with_if_version_match: true,
385 delete_with_if_version_not_match: true,
386 delete_max_size: Some(100),
387
388 copy: true,
389 copy_with_if_version_match: true,
390 copy_with_if_version_not_match: true,
391 copy_can_multi: true,
392 copy_multi_min_size: Some(GCS_REWRITE_MIN_CHUNK_SIZE),
397 copy_multi_max_size: Some(GCS_REWRITE_MAX_CHUNK_SIZE),
398
399 compose: true,
400 compose_with_content_type: true,
401 compose_with_content_disposition: true,
402 compose_with_content_encoding: true,
403 compose_with_cache_control: true,
404 compose_with_user_metadata: true,
405 compose_with_if_not_exists: true,
406 compose_with_if_version_match: true,
407 compose_with_source_version: true,
408
409 list: true,
410 list_with_limit: true,
411 list_with_start_after: true,
412 list_with_recursive: true,
413
414 presign: true,
415 presign_stat: true,
416 presign_read: true,
417 presign_write: true,
418
419 shared: true,
420
421 ..Default::default()
422 };
423
424 let backend = GcsBackend {
425 core: Arc::new(GcsCore {
426 info,
427 capability,
428 endpoint,
429 bucket: bucket.to_string(),
430 root,
431 signer,
432 sign_ctx,
433 predefined_acl: self.config.predefined_acl.clone(),
434 default_storage_class: self.config.default_storage_class.clone(),
435 skip_signature,
436 }),
437 };
438
439 Ok(backend)
440 }
441}
442
443#[derive(Clone, Debug)]
445pub struct GcsBackend {
446 pub(crate) core: Arc<GcsCore>,
447}
448
449impl Service for GcsBackend {
450 type Reader = oio::StreamReader<GcsReader>;
451 type Writer = GcsWriters;
452 type Lister = oio::PageLister<GcsLister>;
453 type Deleter = oio::BatchDeleter<GcsDeleter>;
454 type Copier = GcsCopier;
455 type Composer = GcsComposer;
456
457 fn info(&self) -> ServiceInfo {
458 self.core.info.clone()
459 }
460
461 fn capability(&self) -> Capability {
462 self.core.capability
463 }
464
465 async fn create_dir(
466 &self,
467 _ctx: &OperationContext,
468 _path: &str,
469 _args: OpCreateDir,
470 ) -> Result<RpCreateDir> {
471 Err(Error::new(
472 ErrorKind::Unsupported,
473 "operation is not supported",
474 ))
475 }
476
477 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
478 let error_ctx = ErrorContext::new(ServiceOperation("GetObject"))
479 .with_caller_condition(args.is_conditional());
480 let resp = self.core.gcs_get_object_metadata(ctx, path, &args).await?;
481
482 if !resp.status().is_success() {
483 return Err(parse_error(error_ctx, resp));
484 }
485
486 let slc = resp.into_body();
487 let m = GcsCore::build_metadata_from_object_response(path, slc)?;
488
489 Ok(RpStat::new(m))
490 }
491 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
492 let output: oio::StreamReader<GcsReader> = {
493 Ok(oio::StreamReader::new(GcsReader::new(
494 self.clone(),
495 ctx.clone(),
496 path,
497 args,
498 )))
499 }?;
500
501 Ok(output)
502 }
503
504 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
505 let conditional = args.if_not_exists()
506 || args.if_version_match().is_some()
507 || args.if_version_not_match().is_some();
508 let output: GcsWriters = if conditional {
509 TwoWays::Two(GcsConditionalWriter::new(
510 self.core.clone(),
511 ctx.clone(),
512 path,
513 args,
514 ))
515 } else {
516 let concurrent = args.concurrent();
517 let w = GcsWriter::new(self.core.clone(), ctx.clone(), path, args);
518 let w = oio::MultipartWriter::new(ctx.executor().clone(), w, concurrent);
521 TwoWays::One(w)
522 };
523
524 Ok(output)
525 }
526
527 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
528 let output: oio::BatchDeleter<GcsDeleter> = {
529 Ok(oio::BatchDeleter::new(
530 GcsDeleter::new(self.core.clone(), ctx.clone()),
531 self.core.capability.delete_max_size,
532 ))
533 }?;
534
535 Ok(output)
536 }
537
538 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
539 let output: oio::PageLister<GcsLister> = {
540 let l = GcsLister::new(
541 self.core.clone(),
542 ctx.clone(),
543 path,
544 args.recursive(),
545 args.limit(),
546 args.start_after(),
547 );
548
549 Ok(oio::PageLister::new(l))
550 }?;
551
552 Ok(output)
553 }
554
555 fn copy(
556 &self,
557 ctx: &OperationContext,
558 from: &str,
559 to: &str,
560 args: OpCopy,
561 ) -> Result<Self::Copier> {
562 let output: GcsCopier = {
563 let copier = GcsCopier::new(self.core.clone(), ctx.clone(), from, to, args);
564 Ok(copier)
565 }?;
566
567 Ok(output)
568 }
569
570 fn compose(&self, ctx: &OperationContext, to: &str, args: OpCompose) -> Result<Self::Composer> {
571 if args.if_match().is_some()
572 || args.if_none_match().is_some()
573 || args.if_version_not_match().is_some()
574 {
575 return Err(Error::new(
576 ErrorKind::Unsupported,
577 "GCS compose does not support the requested destination option",
578 ));
579 }
580 Ok(GcsComposer::new(self.core.clone(), ctx.clone(), to, args))
581 }
582
583 async fn rename(
584 &self,
585 _ctx: &OperationContext,
586 _from: &str,
587 _to: &str,
588 _args: OpRename,
589 ) -> Result<RpRename> {
590 Err(Error::new(
591 ErrorKind::Unsupported,
592 "operation is not supported",
593 ))
594 }
595
596 async fn presign(
597 &self,
598 ctx: &OperationContext,
599 path: &str,
600 args: OpPresign,
601 ) -> Result<RpPresign> {
602 let req = match args.operation() {
604 PresignOperation::Stat(v) => self.core.gcs_head_object_xml_request(path, v),
605 PresignOperation::Read(range, v) => {
606 self.core.gcs_get_object_xml_request(path, *range, v)
607 }
608 PresignOperation::Write(v) => {
609 self.core
610 .gcs_insert_object_xml_request(path, v, Buffer::new())
611 }
612 PresignOperation::Delete(_) => Err(Error::new(
613 ErrorKind::Unsupported,
614 "operation is not supported",
615 )),
616 _ => Err(Error::new(
617 ErrorKind::Unsupported,
618 "operation is not supported",
619 )),
620 };
621 let req = req?;
622 let req = self.core.sign_query(ctx, req, args.expire()).await?;
623
624 let (parts, _) = req.into_parts();
626
627 Ok(RpPresign::new(PresignedRequest::new(
628 parts.method,
629 parts.uri,
630 parts.headers,
631 )))
632 }
633}