//! Azure Blob Storage: `az://ACCOUNT/CONTAINER/PREFIX`. //! //! Credentials: a service principal (`AZURE_CLIENT_ID`, //! `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID`, as the Azure SDKs name them, //! or the `AZURE_STORAGE_*` spellings), an account key //! (`AZURE_STORAGE_ACCOUNT_KEY`), a SAS token (`AZURE_STORAGE_SAS_TOKEN`), //! else what `az login` left behind, else the machine's managed //! identity. `?endpoint=URL` points at an emulator. use crate::StoreUrl; use object_store::azure::{MicrosoftAzure, MicrosoftAzureBuilder}; use oxy_core::strata::{Error, Result}; pub const HINT: &str = "run `az login`, or set AZURE_CLIENT_ID, AZURE_CLIENT_SECRET and AZURE_TENANT_ID for a service principal, or AZURE_STORAGE_ACCOUNT_KEY"; fn env(name: &str) -> Option { std::env::var(name).ok().filter(|v| !v.is_empty()) } /// The store and the prefix under the container. pub fn open(url: &StoreUrl) -> Result<(MicrosoftAzure, String)> { let (container, prefix) = match url.path.split_once('/') { Some((c, p)) => (c.to_string(), p.to_string()), None if !url.path.is_empty() => (url.path.clone(), String::new()), None => { return Err(Error::Backend(format!( "{}: an Azure store URL names the account and the container: az://ACCOUNT/CONTAINER/PREFIX", url.plain() ))) } }; let mut b = MicrosoftAzureBuilder::from_env() .with_account(&url.host) .with_container_name(&container) .with_http_connector(crate::http::UreqConnector); let principal = ( env("AZURE_CLIENT_ID").or_else(|| env("AZURE_STORAGE_CLIENT_ID")), env("AZURE_CLIENT_SECRET").or_else(|| env("AZURE_STORAGE_CLIENT_SECRET")), env("AZURE_TENANT_ID").or_else(|| env("AZURE_STORAGE_TENANT_ID")), ); let mut explicit = false; if let (Some(id), Some(secret), Some(tenant)) = principal { b = b.with_client_secret_authorization(id, secret, tenant); explicit = true; } if env("AZURE_STORAGE_ACCOUNT_KEY").is_some() || env("AZURE_STORAGE_ACCESS_KEY").is_some() || env("AZURE_STORAGE_SAS_TOKEN").is_some() || env("AZURE_STORAGE_SAS_KEY").is_some() { explicit = true; } if let Some(endpoint) = url.param("endpoint") { b = b .with_endpoint(endpoint.to_string()) .with_allow_http(endpoint.starts_with("http://")); } if !explicit { // What `az login` left behind; the managed identity comes after. b = b.with_use_azure_cli(true); } let store = b .build() .map_err(|e| Error::Backend(format!("{}: {e}: {HINT}", url.plain())))?; Ok((store, prefix)) }