1use std::fmt::Debug;
19use std::sync::Arc;
20
21use http::StatusCode;
22use http::Uri;
23use log::debug;
24use reqsign_aliyun_oss::AssumeRoleCredentialProvider;
25use reqsign_aliyun_oss::AssumeRoleWithOidcCredentialProvider;
26use reqsign_aliyun_oss::EcsRamRoleCredentialProvider;
27use reqsign_aliyun_oss::EnvCredentialProvider;
28use reqsign_aliyun_oss::RequestSigner;
29use reqsign_aliyun_oss::StaticCredentialProvider;
30use reqsign_core::Context;
31use reqsign_core::Env as _;
32use reqsign_core::OsEnv;
33use reqsign_core::ProvideCredentialChain;
34use reqsign_core::Signer;
35use reqsign_core::StaticEnv;
36use reqsign_file_read_tokio::TokioFileRead;
37
38use super::OSS_SCHEME;
39use super::config::OssConfig;
40use super::core::parse_error;
41use super::core::*;
42use super::deleter::OssDeleter;
43use super::lister::OssLister;
44use super::lister::OssListers;
45use super::lister::OssObjectVersionsLister;
46use super::reader::*;
47use super::writer::OssWriter;
48use super::writer::OssWriters;
49use opendal_core::raw::*;
50use opendal_core::*;
51
52const DEFAULT_BATCH_MAX_OPERATIONS: usize = 1000;
53
54#[doc = include_str!("docs.md")]
56#[derive(Default)]
57pub struct OssBuilder {
58 pub(super) config: OssConfig,
59}
60
61impl Debug for OssBuilder {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("OssBuilder")
64 .field("config", &self.config)
65 .finish_non_exhaustive()
66 }
67}
68
69impl OssBuilder {
70 pub fn root(mut self, root: &str) -> Self {
74 self.config.root = if root.is_empty() {
75 None
76 } else {
77 Some(root.to_string())
78 };
79
80 self
81 }
82
83 pub fn bucket(mut self, bucket: &str) -> Self {
85 self.config.bucket = bucket.to_string();
86
87 self
88 }
89
90 pub fn endpoint(mut self, endpoint: &str) -> Self {
92 if !endpoint.is_empty() {
93 self.config.endpoint = Some(endpoint.trim_end_matches('/').to_string())
95 }
96
97 self
98 }
99
100 pub fn addressing_style(mut self, addressing_style: &str) -> Self {
110 self.config.addressing_style = Some(addressing_style.to_string());
111
112 self
113 }
114
115 #[deprecated(
117 since = "0.57.0",
118 note = "OSS versioning capability is enabled by default and this option is no longer needed."
119 )]
120 pub fn enable_versioning(self, _enabled: bool) -> Self {
121 self
122 }
123
124 pub fn presign_endpoint(mut self, endpoint: &str) -> Self {
133 if !endpoint.is_empty() {
134 self.config.presign_endpoint = Some(endpoint.trim_end_matches('/').to_string())
136 }
137
138 self
139 }
140
141 pub fn presign_addressing_style(mut self, addressing_style: &str) -> Self {
149 self.config.presign_addressing_style = Some(addressing_style.to_string());
150
151 self
152 }
153
154 pub fn access_key_id(mut self, v: &str) -> Self {
159 if !v.is_empty() {
160 self.config.access_key_id = Some(v.to_string())
161 }
162
163 self
164 }
165
166 pub fn access_key_secret(mut self, v: &str) -> Self {
171 if !v.is_empty() {
172 self.config.access_key_secret = Some(v.to_string())
173 }
174
175 self
176 }
177
178 pub fn security_token(mut self, security_token: &str) -> Self {
183 if !security_token.is_empty() {
184 self.config.security_token = Some(security_token.to_string())
185 }
186
187 self
188 }
189
190 fn parse_endpoint(
192 &self,
193 endpoint: &Option<String>,
194 bucket: &str,
195 addressing_style: AddressingStyle,
196 ) -> Result<(String, String)> {
197 let (endpoint, host) = match endpoint.clone() {
198 Some(ep) => {
199 let uri = ep.parse::<Uri>().map_err(|err| {
200 Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
201 .with_context("service", OSS_SCHEME)
202 .with_context("endpoint", &ep)
203 .set_source(err)
204 })?;
205 let host = uri.host().ok_or_else(|| {
206 Error::new(ErrorKind::ConfigInvalid, "endpoint host is empty")
207 .with_context("service", OSS_SCHEME)
208 .with_context("endpoint", &ep)
209 })?;
210 let full_host = match addressing_style {
211 AddressingStyle::Virtual => {
212 if let Some(port) = uri.port_u16() {
213 format!("{bucket}.{host}:{port}")
214 } else {
215 format!("{bucket}.{host}")
216 }
217 }
218 AddressingStyle::Cname | AddressingStyle::Path => {
219 if let Some(port) = uri.port_u16() {
220 format!("{host}:{port}")
221 } else {
222 host.to_string()
223 }
224 }
225 };
226 if let Some(port) = uri.port_u16() {
227 format!("{bucket}.{host}:{port}")
228 } else {
229 format!("{bucket}.{host}")
230 };
231 let endpoint = match uri.scheme_str() {
232 Some(scheme_str) => match scheme_str {
233 "http" | "https" => format!("{scheme_str}://{full_host}"),
234 _ => {
235 return Err(Error::new(
236 ErrorKind::ConfigInvalid,
237 "endpoint protocol is invalid",
238 )
239 .with_context("service", OSS_SCHEME));
240 }
241 },
242 None => format!("https://{full_host}"),
243 };
244 let endpoint = match addressing_style {
245 AddressingStyle::Path => format!("{}/{}", endpoint, bucket),
246 AddressingStyle::Cname | AddressingStyle::Virtual => endpoint,
247 };
248 (endpoint, full_host)
249 }
250 None => {
251 return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
252 .with_context("service", OSS_SCHEME));
253 }
254 };
255 Ok((endpoint, host))
256 }
257
258 pub fn server_side_encryption(mut self, v: &str) -> Self {
275 if !v.is_empty() {
276 self.config.server_side_encryption = Some(v.to_string())
277 }
278 self
279 }
280
281 pub fn server_side_encryption_key_id(mut self, v: &str) -> Self {
287 if !v.is_empty() {
288 self.config.server_side_encryption_key_id = Some(v.to_string())
289 }
290 self
291 }
292
293 #[deprecated(
295 since = "0.57.0",
296 note = "OSS delete batch capability is enabled by default and this option is no longer needed."
297 )]
298 pub fn batch_max_operations(self, _delete_max_size: usize) -> Self {
299 self
300 }
301
302 #[deprecated(
304 since = "0.57.0",
305 note = "OSS delete batch capability is enabled by default and this option is no longer needed."
306 )]
307 pub fn delete_max_size(self, _delete_max_size: usize) -> Self {
308 self
309 }
310
311 pub fn skip_signature(mut self) -> Self {
313 self.config.skip_signature = true;
314 self
315 }
316
317 #[deprecated(
320 since = "0.57.0",
321 note = "Please use `skip_signature` instead of `allow_anonymous`"
322 )]
323 pub fn allow_anonymous(self) -> Self {
324 self.skip_signature()
325 }
326
327 pub fn role_arn(mut self, role_arn: &str) -> Self {
332 if !role_arn.is_empty() {
333 self.config.role_arn = Some(role_arn.to_string())
334 }
335
336 self
337 }
338
339 pub fn role_session_name(mut self, role_session_name: &str) -> Self {
341 if !role_session_name.is_empty() {
342 self.config.role_session_name = Some(role_session_name.to_string())
343 }
344
345 self
346 }
347
348 pub fn oidc_provider_arn(mut self, oidc_provider_arn: &str) -> Self {
350 if !oidc_provider_arn.is_empty() {
351 self.config.oidc_provider_arn = Some(oidc_provider_arn.to_string())
352 }
353
354 self
355 }
356
357 pub fn oidc_token_file(mut self, oidc_token_file: &str) -> Self {
359 if !oidc_token_file.is_empty() {
360 self.config.oidc_token_file = Some(oidc_token_file.to_string())
361 }
362
363 self
364 }
365
366 pub fn sts_endpoint(mut self, sts_endpoint: &str) -> Self {
368 if !sts_endpoint.is_empty() {
369 self.config.sts_endpoint = Some(sts_endpoint.to_string())
370 }
371
372 self
373 }
374
375 pub fn external_id(mut self, v: &str) -> Self {
377 if !v.is_empty() {
378 self.config.external_id = Some(v.to_string())
379 }
380
381 self
382 }
383}
384
385enum AddressingStyle {
386 Path,
387 Cname,
388 Virtual,
389}
390
391impl TryFrom<&Option<String>> for AddressingStyle {
392 type Error = Error;
393
394 fn try_from(value: &Option<String>) -> Result<Self> {
395 match value.as_deref() {
396 None | Some("virtual") => Ok(AddressingStyle::Virtual),
397 Some("path") => Ok(AddressingStyle::Path),
398 Some("cname") => Ok(AddressingStyle::Cname),
399 Some(v) => Err(Error::new(
400 ErrorKind::ConfigInvalid,
401 "Invalid addressing style, available: `virtual`, `path`, `cname`",
402 )
403 .with_context("service", OSS_SCHEME)
404 .with_context("addressing_style", v)),
405 }
406 }
407}
408
409impl Builder for OssBuilder {
410 type Config = OssConfig;
411
412 fn build(self) -> Result<impl Service> {
413 debug!("backend build started: {:?}", self);
414
415 #[allow(deprecated)]
416 let skip_signature = self.config.skip_signature || self.config.allow_anonymous;
417
418 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
419 debug!("backend use root {}", root);
420
421 let bucket = match self.config.bucket.is_empty() {
423 false => Ok(&self.config.bucket),
424 true => Err(
425 Error::new(ErrorKind::ConfigInvalid, "The bucket is misconfigured")
426 .with_context("service", OSS_SCHEME),
427 ),
428 }?;
429
430 let (endpoint, host) = self.parse_endpoint(
433 &self.config.endpoint,
434 bucket,
435 (&self.config.addressing_style).try_into()?,
436 )?;
437 debug!("backend use bucket {}, endpoint: {}", bucket, endpoint);
438
439 let presign_endpoint = if self.config.presign_endpoint.is_some() {
440 self.parse_endpoint(
441 &self.config.presign_endpoint,
442 bucket,
443 (&self.config.presign_addressing_style).try_into()?,
444 )?
445 .0
446 } else {
447 endpoint.clone()
448 };
449 debug!("backend use presign_endpoint: {}", presign_endpoint);
450
451 let server_side_encryption = match &self.config.server_side_encryption {
452 None => None,
453 Some(v) => Some(
454 build_header_value(v)
455 .map_err(|err| err.with_context("key", "server_side_encryption"))?,
456 ),
457 };
458
459 let server_side_encryption_key_id = match &self.config.server_side_encryption_key_id {
460 None => None,
461 Some(v) => Some(
462 build_header_value(v)
463 .map_err(|err| err.with_context("key", "server_side_encryption_key_id"))?,
464 ),
465 };
466
467 let os_env = OsEnv;
472 let mut envs = os_env.vars();
473
474 if let Some(v) = &self.config.role_arn {
475 envs.insert("ALIBABA_CLOUD_ROLE_ARN".to_string(), v.clone());
476 }
477 if let Some(v) = &self.config.oidc_provider_arn {
478 envs.insert("ALIBABA_CLOUD_OIDC_PROVIDER_ARN".to_string(), v.clone());
479 }
480 if let Some(v) = &self.config.oidc_token_file {
481 envs.insert("ALIBABA_CLOUD_OIDC_TOKEN_FILE".to_string(), v.clone());
482 }
483
484 let mut assume_role = AssumeRoleWithOidcCredentialProvider::new();
485
486 if let Some(sts_endpoint) = &self.config.sts_endpoint {
487 if sts_endpoint.starts_with("http://") || sts_endpoint.starts_with("https://") {
488 assume_role = assume_role.with_sts_endpoint(sts_endpoint.clone());
489 } else {
490 envs.insert(
491 "ALIBABA_CLOUD_STS_ENDPOINT".to_string(),
492 sts_endpoint.clone(),
493 );
494 }
495 }
496
497 if let Some(role_session_name) = &self.config.role_session_name {
498 assume_role = assume_role.with_role_session_name(role_session_name.clone());
499 }
500
501 let ctx = Context::new()
502 .with_file_read(TokioFileRead)
503 .with_env(StaticEnv {
504 home_dir: os_env.home_dir(),
505 envs,
506 });
507
508 let static_provider = if let (Some(ak), Some(sk)) =
509 (&self.config.access_key_id, &self.config.access_key_secret)
510 {
511 Some(if let Some(token) = self.config.security_token.as_deref() {
512 StaticCredentialProvider::new(ak, sk).with_security_token(token)
513 } else {
514 StaticCredentialProvider::new(ak, sk)
515 })
516 } else {
517 None
518 };
519
520 let mut provider = ProvideCredentialChain::new();
521 if let Some(static_provider) = static_provider {
522 provider = provider.push(static_provider);
523 }
524 let mut provider = provider
525 .push(EnvCredentialProvider::new())
526 .push(EcsRamRoleCredentialProvider::new())
527 .push(assume_role);
528
529 if let Some(role_arn) = &self.config.role_arn {
530 let mut assume_role_with_ak = AssumeRoleCredentialProvider::new()
531 .with_base_provider(provider)
532 .with_role_arn(role_arn.clone());
533
534 if let Some(role_session_name) = &self.config.role_session_name {
535 assume_role_with_ak =
536 assume_role_with_ak.with_role_session_name(role_session_name.clone());
537 }
538 if let Some(external_id) = &self.config.external_id {
539 assume_role_with_ak = assume_role_with_ak.with_external_id(external_id.clone());
540 }
541 if let Some(sts_endpoint) = &self.config.sts_endpoint {
542 assume_role_with_ak = assume_role_with_ak.with_sts_endpoint(sts_endpoint.clone());
543 }
544
545 provider = ProvideCredentialChain::new().push(assume_role_with_ak);
546 }
547
548 let sign_ctx = ctx;
549 let request_signer = RequestSigner::new(bucket);
550 let signer = Signer::new(sign_ctx.clone(), provider, request_signer);
551
552 let info = ServiceInfo::new(OSS_SCHEME, &root, bucket);
553 let capability = Capability {
554 stat: true,
555 stat_with_if_match: true,
556 stat_with_if_none_match: true,
557 stat_with_version: true,
558
559 read: true,
560 read_with_suffix: true,
561
562 read_with_if_match: true,
563 read_with_if_none_match: true,
564 read_with_version: true,
565 read_with_if_modified_since: true,
566 read_with_if_unmodified_since: true,
567
568 write: true,
569 write_can_empty: true,
570 write_can_append: true,
571 write_can_multi: true,
572 write_with_cache_control: true,
573 write_with_content_type: true,
574 write_with_content_disposition: true,
575 write_with_content_encoding: true,
576 write_with_if_not_exists: true,
577
578 write_multi_min_size: Some(100 * 1024),
582 write_multi_max_size: if cfg!(target_pointer_width = "64") {
586 Some(5 * 1024 * 1024 * 1024)
587 } else {
588 Some(usize::MAX)
589 },
590 write_with_user_metadata: true,
591
592 delete: true,
593 delete_with_version: true,
594 delete_max_size: Some(DEFAULT_BATCH_MAX_OPERATIONS),
595
596 copy: true,
597
598 list: true,
599 list_with_limit: true,
600 list_with_start_after: true,
601 list_with_recursive: true,
602 list_with_versions: true,
603 list_with_deleted: true,
604
605 presign: true,
606 presign_stat: true,
607 presign_read: true,
608 presign_write: true,
609
610 shared: true,
611
612 ..Default::default()
613 };
614
615 Ok(OssBackend {
616 core: Arc::new(OssCore {
617 info,
618 capability,
619 root,
620 bucket: bucket.to_owned(),
621 endpoint,
622 host,
623 presign_endpoint,
624 skip_signature,
625 signer,
626 sign_ctx,
627 server_side_encryption,
628 server_side_encryption_key_id,
629 }),
630 })
631 }
632}
633
634#[derive(Debug, Clone)]
635pub struct OssBackend {
637 pub(crate) core: Arc<OssCore>,
638}
639
640impl Service for OssBackend {
641 type Reader = oio::StreamReader<OssReader>;
642 type Writer = OssWriters;
643 type Lister = OssListers;
644 type Deleter = oio::BatchDeleter<OssDeleter>;
645 type Copier = oio::OneShotCopier;
646
647 fn info(&self) -> ServiceInfo {
648 self.core.info.clone()
649 }
650
651 fn capability(&self) -> Capability {
652 self.core.capability
653 }
654
655 async fn create_dir(
656 &self,
657 _ctx: &OperationContext,
658 _path: &str,
659 _args: OpCreateDir,
660 ) -> Result<RpCreateDir> {
661 Err(Error::new(
662 ErrorKind::Unsupported,
663 "operation is not supported",
664 ))
665 }
666
667 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
668 let resp = self.core.oss_head_object(ctx, path, &args).await?;
669
670 let status = resp.status();
671
672 match status {
673 StatusCode::OK => {
674 let headers = resp.headers();
675 let mut meta = self.core.parse_metadata(path, resp.headers())?;
676
677 if let Some(v) = parse_header_to_str(headers, constants::X_OSS_VERSION_ID)? {
678 meta.set_version(v);
679 }
680
681 Ok(RpStat::new(meta))
682 }
683 _ => Err(parse_error(resp)),
684 }
685 }
686 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
687 let output: oio::StreamReader<OssReader> = {
688 Ok(oio::StreamReader::new(OssReader::new(
689 self.clone(),
690 ctx.clone(),
691 path,
692 args,
693 )))
694 }?;
695
696 Ok(output)
697 }
698
699 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
700 let output: OssWriters = {
701 let writer = OssWriter::new(self.core.clone(), ctx.clone(), path, args.clone());
702
703 let w = if args.append() {
704 OssWriters::Two(oio::AppendWriter::new(writer))
705 } else {
706 OssWriters::One(oio::MultipartWriter::new(
707 ctx.executor().clone(),
708 writer,
709 args.concurrent(),
710 ))
711 };
712
713 Ok(w)
714 }?;
715
716 Ok(output)
717 }
718
719 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
720 let output: oio::BatchDeleter<OssDeleter> = {
721 Ok(oio::BatchDeleter::new(
722 OssDeleter::new(self.core.clone(), ctx.clone()),
723 self.core.capability.delete_max_size,
724 ))
725 }?;
726
727 Ok(output)
728 }
729
730 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
731 let output: OssListers = {
732 let l = if args.versions() || args.deleted() {
733 TwoWays::Two(oio::PageLister::new(OssObjectVersionsLister::new(
734 self.core.clone(),
735 ctx.clone(),
736 path,
737 args,
738 )))
739 } else {
740 TwoWays::One(oio::PageLister::new(OssLister::new(
741 self.core.clone(),
742 ctx.clone(),
743 path,
744 args.recursive(),
745 args.limit(),
746 args.start_after(),
747 )))
748 };
749
750 Ok(l)
751 }?;
752
753 Ok(output)
754 }
755
756 fn copy(
757 &self,
758 ctx: &OperationContext,
759 from: &str,
760 to: &str,
761 _args: OpCopy,
762 _opts: OpCopier,
763 ) -> Result<Self::Copier> {
764 let core = self.core.clone();
765 let ctx = ctx.clone();
766 let from = from.to_string();
767 let to = to.to_string();
768 Ok(oio::OneShotCopier::new(async move {
769 let resp = core.oss_copy_object(&ctx, &from, &to).await?;
770 let status = resp.status();
771
772 match status {
773 StatusCode::OK => Ok(Metadata::default()),
774 _ => Err(parse_error(resp)),
775 }
776 }))
777 }
778
779 async fn rename(
780 &self,
781 _ctx: &OperationContext,
782 _from: &str,
783 _to: &str,
784 _args: OpRename,
785 ) -> Result<RpRename> {
786 Err(Error::new(
787 ErrorKind::Unsupported,
788 "operation is not supported",
789 ))
790 }
791
792 async fn presign(
793 &self,
794 ctx: &OperationContext,
795 path: &str,
796 args: OpPresign,
797 ) -> Result<RpPresign> {
798 let req = match args.operation() {
800 PresignOperation::Stat(v) => self.core.oss_head_object_request(path, true, v),
801 PresignOperation::Read(range, v) => {
802 self.core.oss_get_object_request(path, true, *range, v)
803 }
804 PresignOperation::Write(v) => {
805 self.core
806 .oss_put_object_request(path, None, v, Buffer::new(), true)
807 }
808 PresignOperation::Delete(_) => Err(Error::new(
809 ErrorKind::Unsupported,
810 "operation is not supported",
811 )),
812 _ => Err(Error::new(
813 ErrorKind::Unsupported,
814 "operation is not supported",
815 )),
816 };
817 let req = req?;
818 let req = self.core.sign_query(ctx, req, args.expire()).await?;
819
820 let (parts, _) = req.into_parts();
822
823 Ok(RpPresign::new(PresignedRequest::new(
824 parts.method,
825 parts.uri,
826 parts.headers,
827 )))
828 }
829}