OXYGENHUB
# 810691ca6ce6 oxygen / crates / oxy-strata / src / gcp.rs
2.5 KiB · Rust · last changed Strata: S3, Google Cloud Storage and Azure Blob Storage stores 1 month ago source blame history raw
1
//! Google Cloud Storage: `gs://BUCKET/PREFIX`.
2
//!
3
//! Credentials, in Google's own order: a service account key file named
4
//! by `?credentials=PATH` or `GOOGLE_APPLICATION_CREDENTIALS` (which may
5
//! also hold application default credentials), the key or path in
6
//! `GOOGLE_SERVICE_ACCOUNT*`, then the application default credentials
7
//! `gcloud auth application-default login` wrote, then the instance's
8
//! service account. `?endpoint=URL` points at an emulator, and
9
//! `?anonymous=1` skips signing for one.
10
11
use crate::StoreUrl;
12
use object_store::gcp::{GoogleCloudStorage, GoogleCloudStorageBuilder};
13
use oxy_core::strata::{Error, Result};
14
15
pub const HINT: &str = "run `gcloud auth application-default login`, or set GOOGLE_APPLICATION_CREDENTIALS (or add ?credentials=PATH to the remote URL) to a service account key file";
16
17
/// Whether a credentials file is a service account key, as opposed to
18
/// application default credentials.
19
fn is_service_account_key(path: &str) -> bool {
20
    std::fs::read_to_string(path)
21
        .ok()
22
        .and_then(|t| serde_json::from_str::<serde_json::Value>(&t).ok())
23
        .and_then(|v| {
24
            v.get("type")
25
                .and_then(|t| t.as_str())
26
                .map(|t| t == "service_account")
27
        })
28
        .unwrap_or(false)
29
}
30
31
pub fn open(url: &StoreUrl) -> Result<GoogleCloudStorage> {
32
    let mut b = GoogleCloudStorageBuilder::from_env().with_bucket_name(&url.host);
33
    let file = url.param("credentials").map(str::to_string).or_else(|| {
34
        std::env::var("GOOGLE_APPLICATION_CREDENTIALS")
35
            .ok()
36
            .filter(|v| !v.is_empty())
37
    });
38
    if let Some(path) = file {
39
        if !std::path::Path::new(&path).is_file() {
40
            return Err(Error::Backend(format!(
41
                "{}: the credentials file {path} does not exist: {HINT}",
42
                url.plain()
43
            )));
44
        }
45
        b = if is_service_account_key(&path) {
46
            b.with_service_account_path(path)
47
        } else {
48
            b.with_application_credentials(path)
49
        };
50
    }
51
    if let Some(endpoint) = url.param("endpoint") {
52
        b = b.with_base_url(endpoint);
53
    }
54
    if url.param("anonymous").is_some_and(|v| v != "0") {
55
        // An emulator checks no token, but the client would still ask the
56
        // metadata server for one: give it a token to send.
57
        b = b.with_bearer_token("anonymous").with_skip_signature(true);
58
    }
59
    b.build()
60
        .map_err(|e| Error::Backend(format!("{}: {e}: {HINT}", url.plain())))
61
}