1use std::collections::HashMap;
19use std::fmt::Debug;
20use std::sync::Arc;
21
22use http::StatusCode;
23use http::Uri;
24use log::debug;
25use opendal_core::raw::*;
26use opendal_core::*;
27use reqsign_core::Context;
28use reqsign_core::OsEnv;
29use reqsign_core::ProvideCredentialChain;
30use reqsign_core::Signer;
31use reqsign_file_read_tokio::TokioFileRead;
32use reqsign_huaweicloud_obs::EnvCredentialProvider;
33use reqsign_huaweicloud_obs::RequestSigner;
34use reqsign_huaweicloud_obs::StaticCredentialProvider;
35
36use super::OBS_SCHEME;
37use super::config::ObsConfig;
38use super::core::constants;
39use super::core::parse_error;
40use super::core::{ErrorContext, ObsCore};
41use super::deleter::ObsDeleter;
42use super::lister::ObsLister;
43use super::reader::*;
44use super::writer::ObsWriter;
45use super::writer::ObsWriters;
46
47#[doc = include_str!("docs.md")]
49#[derive(Default)]
50pub struct ObsBuilder {
51 pub(super) config: ObsConfig,
52}
53
54impl Debug for ObsBuilder {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct("ObsBuilder")
57 .field("config", &self.config)
58 .finish_non_exhaustive()
59 }
60}
61
62impl ObsBuilder {
63 pub fn root(mut self, root: &str) -> Self {
67 self.config.root = if root.is_empty() {
68 None
69 } else {
70 Some(root.to_string())
71 };
72
73 self
74 }
75
76 pub fn endpoint(mut self, endpoint: &str) -> Self {
85 if !endpoint.is_empty() {
86 self.config.endpoint = Some(endpoint.trim_end_matches('/').to_string());
87 }
88
89 self
90 }
91
92 pub fn access_key_id(mut self, access_key_id: &str) -> Self {
96 if !access_key_id.is_empty() {
97 self.config.access_key_id = Some(access_key_id.to_string());
98 }
99
100 self
101 }
102
103 pub fn secret_access_key(mut self, secret_access_key: &str) -> Self {
107 if !secret_access_key.is_empty() {
108 self.config.secret_access_key = Some(secret_access_key.to_string());
109 }
110
111 self
112 }
113
114 pub fn bucket(mut self, bucket: &str) -> Self {
117 if !bucket.is_empty() {
118 self.config.bucket = Some(bucket.to_string());
119 }
120
121 self
122 }
123
124 #[deprecated(
126 since = "0.57.0",
127 note = "OBS versioning capability is not controlled by this option and this option is no longer needed."
128 )]
129 pub fn enable_versioning(self, _enabled: bool) -> Self {
130 self
131 }
132}
133
134impl Builder for ObsBuilder {
135 type Config = ObsConfig;
136
137 fn build(self) -> Result<impl Service> {
138 debug!("backend build started: {:?}", self);
139
140 let root = normalize_root(&self.config.root.unwrap_or_default());
141 debug!("backend use root {root}");
142
143 let bucket = match &self.config.bucket {
144 Some(bucket) => Ok(bucket.to_string()),
145 None => Err(
146 Error::new(ErrorKind::ConfigInvalid, "The bucket is misconfigured")
147 .with_context("service", OBS_SCHEME),
148 ),
149 }?;
150 debug!("backend use bucket {}", bucket);
151
152 let uri = match &self.config.endpoint {
153 Some(endpoint) => endpoint.parse::<Uri>().map_err(|err| {
154 Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
155 .with_context("service", OBS_SCHEME)
156 .set_source(err)
157 }),
158 None => Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
159 .with_context("service", OBS_SCHEME)),
160 }?;
161
162 let scheme = match uri.scheme_str() {
163 Some(scheme) => scheme.to_string(),
164 None => "https".to_string(),
165 };
166
167 let (endpoint, is_obs_default) = {
168 let host = uri.host().unwrap_or_default().to_string();
169 if host.starts_with("obs.")
170 && (host.ends_with(".myhuaweicloud.com") || host.ends_with(".huawei.com"))
171 {
172 (format!("{bucket}.{host}"), true)
173 } else {
174 (host, false)
175 }
176 };
177 debug!("backend use endpoint {}", endpoint);
178
179 let ctx = Context::new().with_file_read(TokioFileRead).with_env(OsEnv);
180
181 let mut provider = ProvideCredentialChain::new().push(EnvCredentialProvider::new());
182
183 if let (Some(ak), Some(sk)) = (&self.config.access_key_id, &self.config.secret_access_key) {
184 let static_provider = StaticCredentialProvider::new(ak, sk);
185 provider = provider.push_front(static_provider);
186 }
187
188 let request_signer = RequestSigner::new(if is_obs_default { &bucket } else { &endpoint });
196 let signer = Signer::new(ctx, provider, request_signer);
197
198 let info = ServiceInfo::new(OBS_SCHEME, &root, &bucket);
199 let capability = Capability {
200 stat: true,
201 stat_with_if_match: true,
202 stat_with_if_none_match: true,
203
204 read: true,
205 read_with_suffix: true,
206
207 read_with_if_match: true,
208 read_with_if_none_match: true,
209 read_with_if_modified_since: true,
210 read_with_if_unmodified_since: true,
211
212 write: true,
213 write_can_empty: true,
214 write_can_append: true,
215 write_can_multi: true,
216 write_with_content_type: true,
217 write_with_cache_control: true,
218 write_multi_min_size: Some(5 * 1024 * 1024),
222 write_multi_max_size: if cfg!(target_pointer_width = "64") {
226 Some(5 * 1024 * 1024 * 1024)
227 } else {
228 Some(usize::MAX)
229 },
230 write_with_user_metadata: true,
231
232 delete: true,
233 copy: true,
234
235 list: true,
236 list_with_recursive: true,
237
238 presign: true,
239 presign_stat: true,
240 presign_read: true,
241 presign_write: true,
242
243 shared: true,
244
245 ..Default::default()
246 };
247
248 debug!("backend build finished");
249 Ok(ObsBackend {
250 core: Arc::new(ObsCore {
251 info,
252 capability,
253 bucket,
254 root,
255 endpoint: format!("{}://{}", scheme, endpoint),
256 signer,
257 }),
258 })
259 }
260}
261
262#[derive(Debug, Clone)]
264pub struct ObsBackend {
265 pub(crate) core: Arc<ObsCore>,
266}
267
268impl Service for ObsBackend {
269 type Reader = oio::StreamReader<ObsReader>;
270 type Writer = ObsWriters;
271 type Lister = oio::PageLister<ObsLister>;
272 type Deleter = oio::OneShotDeleter<ObsDeleter>;
273 type Copier = oio::OneShotCopier;
274 type Composer = ();
275
276 fn info(&self) -> ServiceInfo {
277 self.core.info.clone()
278 }
279
280 fn capability(&self) -> Capability {
281 self.core.capability
282 }
283
284 async fn create_dir(
285 &self,
286 _ctx: &OperationContext,
287 _path: &str,
288 _args: OpCreateDir,
289 ) -> Result<RpCreateDir> {
290 Err(Error::new(
291 ErrorKind::Unsupported,
292 "operation is not supported",
293 ))
294 }
295
296 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
297 let resp = self.core.obs_head_object(ctx, path, &args).await?;
298 let headers = resp.headers();
299
300 let status = resp.status();
301
302 match status {
304 StatusCode::OK => {
305 let meta = parse_into_metadata(path, headers)?;
306 let user_meta = headers
307 .iter()
308 .filter_map(|(name, _)| {
309 name.as_str()
310 .strip_prefix(constants::X_OBS_META_PREFIX)
311 .and_then(|stripped_key| {
312 parse_header_to_str(headers, name)
313 .unwrap_or(None)
314 .map(|val| (stripped_key.to_string(), val.to_string()))
315 })
316 })
317 .collect::<HashMap<_, _>>();
318
319 let mut meta = meta.into_builder();
320 if !user_meta.is_empty() {
321 meta.user_metadata(user_meta);
322 }
323
324 if let Some(v) = parse_header_to_str(headers, constants::X_OBS_VERSION_ID)? {
325 meta.version(v);
326 }
327
328 Ok(RpStat::new(meta.build()))
329 }
330 StatusCode::NOT_FOUND if path.ends_with('/') => {
331 Ok(RpStat::new(MetadataBuilder::dir().build()))
332 }
333 _ => Err(parse_error(
334 ErrorContext::new(ServiceOperation("HeadObject")),
335 resp,
336 )),
337 }
338 }
339 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
340 let output: oio::StreamReader<ObsReader> = {
341 Ok(oio::StreamReader::new(ObsReader::new(
342 self.clone(),
343 ctx.clone(),
344 path,
345 args,
346 )))
347 }?;
348
349 Ok(output)
350 }
351
352 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
353 let output: ObsWriters = {
354 let writer = ObsWriter::new(self.core.clone(), ctx.clone(), path, args.clone());
355
356 let w = if args.append() {
357 ObsWriters::Two(oio::AppendWriter::new(writer))
358 } else {
359 ObsWriters::One(oio::MultipartWriter::new(
360 ctx.executor().clone(),
361 writer,
362 args.concurrent(),
363 ))
364 };
365
366 Ok(w)
367 }?;
368
369 Ok(output)
370 }
371
372 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
373 let output: oio::OneShotDeleter<ObsDeleter> = {
374 Ok(oio::OneShotDeleter::new(ObsDeleter::new(
375 self.core.clone(),
376 ctx.clone(),
377 )))
378 }?;
379
380 Ok(output)
381 }
382
383 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
384 let output: oio::PageLister<ObsLister> = {
385 let l = ObsLister::new(
386 self.core.clone(),
387 ctx.clone(),
388 path,
389 args.recursive(),
390 args.limit(),
391 );
392 Ok(oio::PageLister::new(l))
393 }?;
394
395 Ok(output)
396 }
397
398 fn copy(
399 &self,
400 ctx: &OperationContext,
401 from: &str,
402 to: &str,
403 args: OpCopy,
404 ) -> Result<Self::Copier> {
405 let backend = self.clone();
406 let core = self.core.clone();
407 let ctx = ctx.clone();
408 let from = from.to_string();
409 let to = to.to_string();
410 let source_content_length_hint = args.source_content_length_hint();
411 Ok(oio::OneShotCopier::new(async move {
412 let source_size = match source_content_length_hint {
413 Some(size) => size,
414 None => backend
415 .stat(&ctx, &from, OpStat::default())
416 .await?
417 .into_metadata()
418 .content_length(),
419 };
420
421 let resp = core.obs_copy_object(&ctx, &from, &to).await?;
422
423 let status = resp.status();
424
425 match status {
426 StatusCode::OK => Ok(MetadataBuilder::file(source_size).build()),
427 _ => Err(parse_error(
428 ErrorContext::new(ServiceOperation("CopyObject")),
429 resp,
430 )),
431 }
432 }))
433 }
434
435 async fn rename(
436 &self,
437 _ctx: &OperationContext,
438 _from: &str,
439 _to: &str,
440 _args: OpRename,
441 ) -> Result<RpRename> {
442 Err(Error::new(
443 ErrorKind::Unsupported,
444 "operation is not supported",
445 ))
446 }
447
448 async fn presign(
449 &self,
450 ctx: &OperationContext,
451 path: &str,
452 args: OpPresign,
453 ) -> Result<RpPresign> {
454 let req = match args.operation() {
455 PresignOperation::Stat(v) => self.core.obs_head_object_request(path, v),
456 PresignOperation::Read(range, v) => self.core.obs_get_object_request(path, *range, v),
457 PresignOperation::Write(v) => {
458 self.core
459 .obs_put_object_request(path, None, v, Buffer::new())
460 }
461 PresignOperation::Delete(_) => Err(Error::new(
462 ErrorKind::Unsupported,
463 "operation is not supported",
464 )),
465 _ => Err(Error::new(
466 ErrorKind::Unsupported,
467 "operation is not supported",
468 )),
469 };
470 let req = req?;
471 let req = self.core.sign_query(ctx, req, args.expire()).await?;
472
473 let (parts, _) = req.into_parts();
475
476 Ok(RpPresign::new(PresignedRequest::new(
477 parts.method,
478 parts.uri,
479 parts.headers,
480 )))
481 }
482}