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::ObsCore;
39use super::core::constants;
40use super::core::parse_error;
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
275 fn info(&self) -> ServiceInfo {
276 self.core.info.clone()
277 }
278
279 fn capability(&self) -> Capability {
280 self.core.capability
281 }
282
283 async fn create_dir(
284 &self,
285 _ctx: &OperationContext,
286 _path: &str,
287 _args: OpCreateDir,
288 ) -> Result<RpCreateDir> {
289 Err(Error::new(
290 ErrorKind::Unsupported,
291 "operation is not supported",
292 ))
293 }
294
295 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
296 let resp = self.core.obs_head_object(ctx, path, &args).await?;
297 let headers = resp.headers();
298
299 let status = resp.status();
300
301 match status {
303 StatusCode::OK => {
304 let mut meta = parse_into_metadata(path, headers)?;
305 let user_meta = headers
306 .iter()
307 .filter_map(|(name, _)| {
308 name.as_str()
309 .strip_prefix(constants::X_OBS_META_PREFIX)
310 .and_then(|stripped_key| {
311 parse_header_to_str(headers, name)
312 .unwrap_or(None)
313 .map(|val| (stripped_key.to_string(), val.to_string()))
314 })
315 })
316 .collect::<HashMap<_, _>>();
317
318 if !user_meta.is_empty() {
319 meta = meta.with_user_metadata(user_meta);
320 }
321
322 if let Some(v) = parse_header_to_str(headers, constants::X_OBS_VERSION_ID)? {
323 meta.set_version(v);
324 }
325
326 Ok(RpStat::new(meta))
327 }
328 StatusCode::NOT_FOUND if path.ends_with('/') => {
329 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
330 }
331 _ => Err(parse_error(resp)),
332 }
333 }
334 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
335 let output: oio::StreamReader<ObsReader> = {
336 Ok(oio::StreamReader::new(ObsReader::new(
337 self.clone(),
338 ctx.clone(),
339 path,
340 args,
341 )))
342 }?;
343
344 Ok(output)
345 }
346
347 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
348 let output: ObsWriters = {
349 let writer = ObsWriter::new(self.core.clone(), ctx.clone(), path, args.clone());
350
351 let w = if args.append() {
352 ObsWriters::Two(oio::AppendWriter::new(writer))
353 } else {
354 ObsWriters::One(oio::MultipartWriter::new(
355 ctx.executor().clone(),
356 writer,
357 args.concurrent(),
358 ))
359 };
360
361 Ok(w)
362 }?;
363
364 Ok(output)
365 }
366
367 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
368 let output: oio::OneShotDeleter<ObsDeleter> = {
369 Ok(oio::OneShotDeleter::new(ObsDeleter::new(
370 self.core.clone(),
371 ctx.clone(),
372 )))
373 }?;
374
375 Ok(output)
376 }
377
378 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
379 let output: oio::PageLister<ObsLister> = {
380 let l = ObsLister::new(
381 self.core.clone(),
382 ctx.clone(),
383 path,
384 args.recursive(),
385 args.limit(),
386 );
387 Ok(oio::PageLister::new(l))
388 }?;
389
390 Ok(output)
391 }
392
393 fn copy(
394 &self,
395 ctx: &OperationContext,
396 from: &str,
397 to: &str,
398 _args: OpCopy,
399 _opts: OpCopier,
400 ) -> Result<Self::Copier> {
401 let core = self.core.clone();
402 let ctx = ctx.clone();
403 let from = from.to_string();
404 let to = to.to_string();
405 Ok(oio::OneShotCopier::new(async move {
406 let resp = core.obs_copy_object(&ctx, &from, &to).await?;
407
408 let status = resp.status();
409
410 match status {
411 StatusCode::OK => Ok(Metadata::default()),
412 _ => Err(parse_error(resp)),
413 }
414 }))
415 }
416
417 async fn rename(
418 &self,
419 _ctx: &OperationContext,
420 _from: &str,
421 _to: &str,
422 _args: OpRename,
423 ) -> Result<RpRename> {
424 Err(Error::new(
425 ErrorKind::Unsupported,
426 "operation is not supported",
427 ))
428 }
429
430 async fn presign(
431 &self,
432 ctx: &OperationContext,
433 path: &str,
434 args: OpPresign,
435 ) -> Result<RpPresign> {
436 let req = match args.operation() {
437 PresignOperation::Stat(v) => self.core.obs_head_object_request(path, v),
438 PresignOperation::Read(range, v) => self.core.obs_get_object_request(path, *range, v),
439 PresignOperation::Write(v) => {
440 self.core
441 .obs_put_object_request(path, None, v, Buffer::new())
442 }
443 PresignOperation::Delete(_) => Err(Error::new(
444 ErrorKind::Unsupported,
445 "operation is not supported",
446 )),
447 _ => Err(Error::new(
448 ErrorKind::Unsupported,
449 "operation is not supported",
450 )),
451 };
452 let req = req?;
453 let req = self.core.sign_query(ctx, req, args.expire()).await?;
454
455 let (parts, _) = req.into_parts();
457
458 Ok(RpPresign::new(PresignedRequest::new(
459 parts.method,
460 parts.uri,
461 parts.headers,
462 )))
463 }
464}