//! Google Cloud Storage: `gs://BUCKET/PREFIX`. //! //! Credentials, in Google's own order: a service account key file named //! by `?credentials=PATH` or `GOOGLE_APPLICATION_CREDENTIALS` (which may //! also hold application default credentials), the key or path in //! `GOOGLE_SERVICE_ACCOUNT*`, then the application default credentials //! `gcloud auth application-default login` wrote, then the instance's //! service account. `?endpoint=URL` points at another endpoint. use crate::StoreUrl; use object_store::gcp::{GoogleCloudStorage, GoogleCloudStorageBuilder}; use oxy_core::strata::{Error, Result}; 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"; /// Whether a credentials file is a service account key, as opposed to /// application default credentials. fn is_service_account_key(path: &str) -> bool { std::fs::read_to_string(path) .ok() .and_then(|t| serde_json::from_str::(&t).ok()) .and_then(|v| { v.get("type") .and_then(|t| t.as_str()) .map(|t| t == "service_account") }) .unwrap_or(false) } /// With `key` (a service account key's JSON) given, only it is used. pub fn open(url: &StoreUrl, key: Option<&str>) -> Result { if let Some(key) = key { let mut b = GoogleCloudStorageBuilder::new() .with_bucket_name(&url.host) .with_http_connector(crate::http::UreqConnector) .with_service_account_key(key); if let Some(endpoint) = url.param("endpoint") { b = b.with_base_url(endpoint); } return b .build() .map_err(|e| Error::Backend(format!("{}: {e}", url.plain()))); } let mut b = GoogleCloudStorageBuilder::from_env() .with_bucket_name(&url.host) .with_http_connector(crate::http::UreqConnector); let file = url.param("credentials").map(str::to_string).or_else(|| { std::env::var("GOOGLE_APPLICATION_CREDENTIALS") .ok() .filter(|v| !v.is_empty()) }); if let Some(path) = file { if !std::path::Path::new(&path).is_file() { return Err(Error::Backend(format!( "{}: the credentials file {path} does not exist: {HINT}", url.plain() ))); } b = if is_service_account_key(&path) { b.with_service_account_path(path) } else { b.with_application_credentials(path) }; } if let Some(endpoint) = url.param("endpoint") { b = b.with_base_url(endpoint); } b.build() .map_err(|e| Error::Backend(format!("{}: {e}: {HINT}", url.plain()))) }