1use std::fmt::Debug;
19use std::sync::Arc;
20
21use http::StatusCode;
22use log::debug;
23use reqsign_azure_storage::Credential;
24use reqsign_azure_storage::DefaultCredentialProvider;
25use reqsign_azure_storage::RequestSigner;
26use reqsign_azure_storage::StaticCredentialProvider;
27use reqsign_core::Context;
28use reqsign_core::Env as _;
29use reqsign_core::OsEnv;
30use reqsign_core::ProvideCredentialChain;
31use reqsign_core::Signer;
32use reqsign_core::StaticEnv;
33use reqsign_file_read_tokio::TokioFileRead;
34
35use super::AZDLS_SCHEME;
36use super::config::AzdlsConfig;
37use super::core::DIRECTORY;
38use super::core::parse_error;
39use super::core::{AzdlsCore, ErrorContext};
40use super::deleter::AzdlsDeleter;
41use super::lister::AzdlsLister;
42use super::reader::*;
43use super::writer::AzdlsLazyPositionWriter;
44use super::writer::AzdlsWriter;
45use super::writer::AzdlsWriters;
46use opendal_core::raw::*;
47use opendal_core::*;
48use opendal_service_azure_common::{
49 AzureStorageConfig as AzureConnectionConfig, AzureStorageService,
50 azure_account_name_from_endpoint, azure_config_from_connection_string,
51};
52
53impl From<AzureConnectionConfig> for AzdlsConfig {
54 fn from(config: AzureConnectionConfig) -> Self {
55 AzdlsConfig {
56 endpoint: config.endpoint,
57 account_name: config.account_name,
58 account_key: config.account_key,
59 client_secret: config.client_secret,
60 tenant_id: config.tenant_id,
61 client_id: config.client_id,
62 sas_token: config.sas_token,
63 authority_host: config.authority_host,
64 ..Default::default()
65 }
66 }
67}
68
69#[doc = include_str!("docs.md")]
71#[derive(Default)]
72pub struct AzdlsBuilder {
73 pub(super) config: AzdlsConfig,
74 pub(super) credential_providers: Option<ProvideCredentialChain<Credential>>,
75}
76
77impl Debug for AzdlsBuilder {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.debug_struct("AzdlsBuilder")
80 .field("config", &self.config)
81 .finish_non_exhaustive()
82 }
83}
84
85impl AzdlsBuilder {
86 pub fn root(mut self, root: &str) -> Self {
90 self.config.root = if root.is_empty() {
91 None
92 } else {
93 Some(root.to_string())
94 };
95
96 self
97 }
98
99 pub fn filesystem(mut self, filesystem: &str) -> Self {
101 self.config.filesystem = filesystem.to_string();
102
103 self
104 }
105
106 pub fn endpoint(mut self, endpoint: &str) -> Self {
113 if !endpoint.is_empty() {
114 self.config.endpoint = Some(endpoint.trim_end_matches('/').to_string());
116 }
117
118 self
119 }
120
121 pub fn account_name(mut self, account_name: &str) -> Self {
126 if !account_name.is_empty() {
127 self.config.account_name = Some(account_name.to_string());
128 }
129
130 self
131 }
132
133 pub fn account_key(mut self, account_key: &str) -> Self {
138 if !account_key.is_empty() {
139 self.config.account_key = Some(account_key.to_string());
140 }
141
142 self
143 }
144
145 pub fn client_secret(mut self, client_secret: &str) -> Self {
151 if !client_secret.is_empty() {
152 self.config.client_secret = Some(client_secret.to_string());
153 }
154
155 self
156 }
157
158 pub fn tenant_id(mut self, tenant_id: &str) -> Self {
164 if !tenant_id.is_empty() {
165 self.config.tenant_id = Some(tenant_id.to_string());
166 }
167
168 self
169 }
170
171 pub fn client_id(mut self, client_id: &str) -> Self {
177 if !client_id.is_empty() {
178 self.config.client_id = Some(client_id.to_string());
179 }
180
181 self
182 }
183
184 pub fn sas_token(mut self, sas_token: &str) -> Self {
186 if !sas_token.is_empty() {
187 self.config.sas_token = Some(sas_token.to_string());
188 }
189
190 self
191 }
192
193 pub fn credential_provider_chain(mut self, chain: ProvideCredentialChain<Credential>) -> Self {
195 self.credential_providers = Some(chain);
196 self
197 }
198
199 pub fn authority_host(mut self, authority_host: &str) -> Self {
205 if !authority_host.is_empty() {
206 self.config.authority_host = Some(authority_host.to_string());
207 }
208
209 self
210 }
211
212 pub fn from_connection_string(conn_str: &str) -> Result<Self> {
234 let config = azure_config_from_connection_string(conn_str, AzureStorageService::Adls)?;
235
236 Ok(AzdlsConfig::from(config).into_builder())
237 }
238
239 pub fn enable_hns(mut self, enable: bool) -> Self {
241 self.config.enable_hns = enable;
242 self
243 }
244}
245
246impl Builder for AzdlsBuilder {
247 type Config = AzdlsConfig;
248
249 fn build(self) -> Result<impl Service> {
250 debug!("backend build started: {:?}", self);
251
252 let root = normalize_root(&self.config.root.unwrap_or_default());
253 debug!("backend use root {root}");
254
255 let filesystem = match self.config.filesystem.is_empty() {
257 false => Ok(&self.config.filesystem),
258 true => Err(Error::new(ErrorKind::ConfigInvalid, "filesystem is empty")
259 .with_operation("Builder::build")
260 .with_context("service", AZDLS_SCHEME)),
261 }?;
262 debug!("backend use filesystem {}", filesystem);
263
264 let endpoint = match &self.config.endpoint {
265 Some(endpoint) => Ok(endpoint.clone().trim_end_matches('/').to_string()),
266 None => Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
267 .with_operation("Builder::build")
268 .with_context("service", AZDLS_SCHEME)),
269 }?;
270 debug!("backend use endpoint {}", endpoint);
271
272 let account_name = self
273 .config
274 .account_name
275 .clone()
276 .or_else(|| azure_account_name_from_endpoint(endpoint.as_str()));
277
278 let mut envs = std::collections::HashMap::new();
279
280 if let Some(v) = &account_name {
281 envs.insert("AZBLOB_ACCOUNT_NAME".to_string(), v.clone());
282 envs.insert("AZURE_STORAGE_ACCOUNT_NAME".to_string(), v.clone());
283 }
284 if let Some(v) = &self.config.account_key {
285 envs.insert("AZBLOB_ACCOUNT_KEY".to_string(), v.clone());
286 envs.insert("AZURE_STORAGE_ACCOUNT_KEY".to_string(), v.clone());
287 }
288 if let Some(v) = &self.config.sas_token {
289 envs.insert("AZURE_STORAGE_SAS_TOKEN".to_string(), v.clone());
290 }
291 if let Some(v) = &self.config.client_id {
292 envs.insert("AZURE_CLIENT_ID".to_string(), v.clone());
293 }
294 if let Some(v) = &self.config.client_secret {
295 envs.insert("AZURE_CLIENT_SECRET".to_string(), v.clone());
296 }
297 if let Some(v) = &self.config.tenant_id {
298 envs.insert("AZURE_TENANT_ID".to_string(), v.clone());
299 }
300 if let Some(v) = &self.config.authority_host {
301 envs.insert("AZURE_AUTHORITY_HOST".to_string(), v.clone());
302 }
303
304 let os_env = OsEnv;
305 let ctx = Context::new()
306 .with_file_read(TokioFileRead)
307 .with_env(StaticEnv {
308 home_dir: os_env.home_dir(),
309 envs,
310 });
311
312 let mut credential_providers =
313 ProvideCredentialChain::new().push(DefaultCredentialProvider::new());
314
315 if let (Some(account_name), Some(account_key)) =
316 (account_name.as_deref(), self.config.account_key.as_deref())
317 {
318 credential_providers = credential_providers.push_front(
319 StaticCredentialProvider::new_shared_key(account_name, account_key),
320 );
321 }
322 if let Some(sas_token) = self.config.sas_token.as_deref() {
323 credential_providers =
324 credential_providers.push_front(StaticCredentialProvider::new_sas_token(sas_token));
325 }
326
327 if let Some(customized_credential_chain) = self.credential_providers {
328 credential_providers = customized_credential_chain;
329 }
330
331 let sign_ctx = ctx;
332 let signer = Signer::new(sign_ctx.clone(), credential_providers, RequestSigner::new());
333
334 let info = ServiceInfo::new(AZDLS_SCHEME, &root, filesystem);
335 let capability = Capability {
336 stat: true,
337 stat_with_if_match: true,
338 stat_with_if_none_match: true,
339 stat_with_if_modified_since: true,
340 stat_with_if_unmodified_since: true,
341
342 read: true,
343 read_with_if_match: true,
344 read_with_if_none_match: true,
345 read_with_if_modified_since: true,
346 read_with_if_unmodified_since: true,
347
348 write: true,
349 write_can_append: true,
350 write_can_multi: true,
351 write_with_if_none_match: true,
352 write_with_if_not_exists: true,
353 write_with_user_metadata: true,
354
355 create_dir: true,
356
357 delete: true,
358 delete_with_if_match: true,
359 delete_with_recursive: true,
360
361 rename: true,
362
363 list: true,
364
365 shared: true,
366
367 ..Default::default()
368 };
369
370 Ok(AzdlsBackend {
371 core: Arc::new(AzdlsCore {
372 info,
373 capability,
374 filesystem: self.config.filesystem.clone(),
375 root,
376 endpoint,
377 enable_hns: self.config.enable_hns,
378 signer,
379 sign_ctx,
380 }),
381 })
382 }
383}
384
385#[derive(Debug, Clone)]
387pub struct AzdlsBackend {
388 pub(crate) core: Arc<AzdlsCore>,
389}
390
391impl Service for AzdlsBackend {
392 type Reader = oio::StreamReader<AzdlsReader>;
393 type Writer = AzdlsWriters;
394 type Lister = oio::PageLister<AzdlsLister>;
395 type Deleter = oio::OneShotDeleter<AzdlsDeleter>;
396 type Copier = ();
397 type Composer = ();
398
399 fn info(&self) -> ServiceInfo {
400 self.core.info.clone()
401 }
402
403 fn capability(&self) -> Capability {
404 self.core.capability
405 }
406
407 async fn create_dir(
408 &self,
409 ctx: &OperationContext,
410 path: &str,
411 _: OpCreateDir,
412 ) -> Result<RpCreateDir> {
413 let resp = self
414 .core
415 .azdls_create(ctx, path, DIRECTORY, &OpWrite::default())
416 .await?;
417
418 let status = resp.status();
419 match status {
420 StatusCode::CREATED | StatusCode::OK => Ok(RpCreateDir::default()),
421 _ => Err(parse_error(
422 ErrorContext::new(ServiceOperation("CreateDirectory")),
423 resp,
424 )),
425 }
426 }
427
428 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
429 if path == "/" {
432 return Ok(RpStat::new(MetadataBuilder::dir().build()));
433 }
434
435 let metadata = self.core.azdls_stat_metadata(ctx, path, &args).await?;
436 Ok(RpStat::new(metadata))
437 }
438 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
439 let output: oio::StreamReader<AzdlsReader> = {
440 Ok(oio::StreamReader::new(AzdlsReader::new(
441 self.clone(),
442 ctx.clone(),
443 path,
444 args,
445 )))
446 }?;
447
448 Ok(output)
449 }
450
451 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
452 let output: AzdlsWriters = {
453 if args.append() {
454 let w = AzdlsWriter::new(
455 self.core.clone(),
456 ctx.clone(),
457 args.clone(),
458 path.to_string(),
459 );
460 Ok(AzdlsWriters::Two(oio::AppendWriter::new(w)))
461 } else {
462 let w = AzdlsWriter::new(
463 self.core.clone(),
464 ctx.clone(),
465 args.clone(),
466 path.to_string(),
467 );
468 let w = oio::PositionWriter::new(
469 ctx.executor().clone(),
470 AzdlsLazyPositionWriter::new(w),
471 args.concurrent(),
472 );
473 Ok(AzdlsWriters::One(w))
474 }
475 }?;
476
477 Ok(output)
478 }
479
480 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
481 let output: oio::OneShotDeleter<AzdlsDeleter> = {
482 Ok(oio::OneShotDeleter::new(AzdlsDeleter::new(
483 self.core.clone(),
484 ctx.clone(),
485 )))
486 }?;
487
488 Ok(output)
489 }
490
491 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
492 let output: oio::PageLister<AzdlsLister> = {
493 let l = AzdlsLister::new(
494 self.core.clone(),
495 ctx.clone(),
496 path.to_string(),
497 args.limit(),
498 );
499
500 Ok(oio::PageLister::new(l))
501 }?;
502
503 Ok(output)
504 }
505
506 fn copy(
507 &self,
508 _ctx: &OperationContext,
509 _from: &str,
510 _to: &str,
511 _args: OpCopy,
512 ) -> Result<Self::Copier> {
513 Err(Error::new(
514 ErrorKind::Unsupported,
515 "operation is not supported",
516 ))
517 }
518
519 async fn rename(
520 &self,
521 ctx: &OperationContext,
522 from: &str,
523 to: &str,
524 _args: OpRename,
525 ) -> Result<RpRename> {
526 if let Some(resp) = self.core.azdls_ensure_parent_path(ctx, to).await? {
527 let status = resp.status();
528 match status {
529 StatusCode::CREATED | StatusCode::CONFLICT => {}
530 _ => {
531 return Err(parse_error(
532 ErrorContext::new(ServiceOperation("CreateDirectory")),
533 resp,
534 ));
535 }
536 }
537 }
538
539 let resp = self.core.azdls_rename(ctx, from, to).await?;
540
541 let status = resp.status();
542
543 match status {
544 StatusCode::CREATED => Ok(RpRename::default()),
545 _ => Err(parse_error(
546 ErrorContext::new(ServiceOperation("RenamePath")),
547 resp,
548 )),
549 }
550 }
551
552 async fn presign(
553 &self,
554 _ctx: &OperationContext,
555 _path: &str,
556 _args: OpPresign,
557 ) -> Result<RpPresign> {
558 Err(Error::new(
559 ErrorKind::Unsupported,
560 "operation is not supported",
561 ))
562 }
563}