OXYGENHUB
◆ v0.5.0 oxygen / crates / oxy-strata / src / azure.rs
3.4 KiB · Rust · last changed Cloud stores take credentials handed over 1 month ago source blame history raw
1
//! Azure Blob Storage: `az://ACCOUNT/CONTAINER/PREFIX`.
2
//!
3
//! Credentials: a service principal (`AZURE_CLIENT_ID`,
4
//! `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID`, as the Azure SDKs name them,
5
//! or the `AZURE_STORAGE_*` spellings), an account key
6
//! (`AZURE_STORAGE_ACCOUNT_KEY`), a SAS token (`AZURE_STORAGE_SAS_TOKEN`),
7
//! else what `az login` left behind, else the machine's managed
8
//! identity. `?endpoint=URL` points at an emulator.
9
10
use crate::StoreUrl;
11
use object_store::azure::{MicrosoftAzure, MicrosoftAzureBuilder};
12
use oxy_core::strata::{Error, Result};
13
14
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";
15
16
fn env(name: &str) -> Option<String> {
17
    std::env::var(name).ok().filter(|v| !v.is_empty())
18
}
19
20
/// The store and the prefix under the container.
21
/// With `principal` (tenant, client id, client secret) given, only it
22
/// is used.
23
pub fn open(
24
    url: &StoreUrl,
25
    principal: Option<(&str, &str, &str)>,
26
) -> Result<(MicrosoftAzure, String)> {
27
    let (container, prefix) = match url.path.split_once('/') {
28
        Some((c, p)) => (c.to_string(), p.to_string()),
29
        None if !url.path.is_empty() => (url.path.clone(), String::new()),
30
        None => {
31
            return Err(Error::Backend(format!(
32
                "{}: an Azure store URL names the account and the container: az://ACCOUNT/CONTAINER/PREFIX",
33
                url.plain()
34
            )))
35
        }
36
    };
37
    if let Some((tenant, id, secret)) = principal {
38
        let mut b = MicrosoftAzureBuilder::new()
39
            .with_account(&url.host)
40
            .with_container_name(&container)
41
            .with_http_connector(crate::http::UreqConnector)
42
            .with_client_secret_authorization(id, secret, tenant);
43
        if let Some(endpoint) = url.param("endpoint") {
44
            b = b
45
                .with_endpoint(endpoint.to_string())
46
                .with_allow_http(endpoint.starts_with("http://"));
47
        }
48
        let store = b
49
            .build()
50
            .map_err(|e| Error::Backend(format!("{}: {e}", url.plain())))?;
51
        return Ok((store, prefix));
52
    }
53
    let mut b = MicrosoftAzureBuilder::from_env()
54
        .with_account(&url.host)
55
        .with_container_name(&container)
56
        .with_http_connector(crate::http::UreqConnector);
57
    let principal = (
58
        env("AZURE_CLIENT_ID").or_else(|| env("AZURE_STORAGE_CLIENT_ID")),
59
        env("AZURE_CLIENT_SECRET").or_else(|| env("AZURE_STORAGE_CLIENT_SECRET")),
60
        env("AZURE_TENANT_ID").or_else(|| env("AZURE_STORAGE_TENANT_ID")),
61
    );
62
    let mut explicit = false;
63
    if let (Some(id), Some(secret), Some(tenant)) = principal {
64
        b = b.with_client_secret_authorization(id, secret, tenant);
65
        explicit = true;
66
    }
67
    if env("AZURE_STORAGE_ACCOUNT_KEY").is_some()
68
        || env("AZURE_STORAGE_ACCESS_KEY").is_some()
69
        || env("AZURE_STORAGE_SAS_TOKEN").is_some()
70
        || env("AZURE_STORAGE_SAS_KEY").is_some()
71
    {
72
        explicit = true;
73
    }
74
    if let Some(endpoint) = url.param("endpoint") {
75
        b = b
76
            .with_endpoint(endpoint.to_string())
77
            .with_allow_http(endpoint.starts_with("http://"));
78
    }
79
    if !explicit {
80
        // What `az login` left behind; the managed identity comes after.
81
        b = b.with_use_azure_cli(true);
82
    }
83
    let store = b
84
        .build()
85
        .map_err(|e| Error::Backend(format!("{}: {e}: {HINT}", url.plain())))?;
86
    Ok((store, prefix))
87
}