use std::env;
use s3::creds::Credentials;
use s3::{Bucket, Region};
use serde::{Deserialize, Serialize};
use xvc_core::R1NStore;
use xvc_core::{info, watch, XvcOutputSender};
use xvc_core::{XvcCachePath, XvcRoot};
use crate::storage::XVC_STORAGE_GUID_FILENAME;
use crate::{Error, Result, XvcStorage, XvcStorageEvent};
use crate::{XvcStorageGuid, XvcStorageOperations};
use super::async_common::XvcS3StorageOperations;
use super::XvcStoragePath;
pub fn cmd_new_s3(
output_snd: &XvcOutputSender,
xvc_root: &XvcRoot,
name: String,
region: String,
bucket_name: String,
storage_prefix: String,
) -> Result<()> {
let mut storage = XvcS3Storage {
guid: XvcStorageGuid::new(),
name,
region,
bucket_name,
storage_prefix,
};
let init_event = storage.init(output_snd, xvc_root)?;
watch!(init_event);
xvc_root.with_r1nstore_mut(|store: &mut R1NStore<XvcStorage, XvcStorageEvent>| {
let store_e = xvc_root.new_entity();
let event_e = xvc_root.new_entity();
store.insert(
store_e,
XvcStorage::S3(storage.clone()),
event_e,
XvcStorageEvent::Init(init_event.clone()),
);
Ok(())
})?;
info!(output_snd, "S3 Storage Created: {:#?}", storage);
Ok(())
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)]
pub struct XvcS3Storage {
pub guid: XvcStorageGuid,
pub name: String,
pub region: String,
pub bucket_name: String,
pub storage_prefix: String,
}
impl XvcS3StorageOperations for XvcS3Storage {
fn storage_prefix(&self) -> String {
self.storage_prefix.clone()
}
fn guid(&self) -> &XvcStorageGuid {
&self.guid
}
fn get_bucket(&self) -> Result<Box<Bucket>> {
let credentials = self.credentials()?;
let region: Region = self.region.parse().expect("Cannot parse region name");
let bucket = Bucket::new(&self.bucket_name, region, credentials)?;
Ok(bucket)
}
fn credentials(&self) -> Result<Credentials> {
let specific_access_key_var = format!("XVC_STORAGE_ACCESS_KEY_ID_{}", self.name);
let specific_secret_key_var = format!("XVC_STORAGE_SECRET_ACCESS_KEY_{}", self.name);
if let (Ok(access_key), Ok(secret_key)) = (
env::var(&specific_access_key_var),
env::var(&specific_secret_key_var),
) {
return Credentials::new(Some(&access_key), Some(&secret_key), None, None, None)
.map_err(|e| e.into());
}
let type_access_key_var = "AWS_ACCESS_KEY_ID";
let type_secret_key_var = "AWS_SECRET_ACCESS_KEY";
if let (Ok(access_key), Ok(secret_key)) =
(env::var(type_access_key_var), env::var(type_secret_key_var))
{
return Credentials::new(Some(&access_key), Some(&secret_key), None, None, None)
.map_err(|e| e.into());
}
Err(Error::CloudCredentialsNotFound {
storage_name: self.name.clone(),
var_pairs: vec![
(specific_access_key_var, specific_secret_key_var),
(
type_access_key_var.to_string(),
type_secret_key_var.to_string(),
),
],
})
}
fn bucket_name(&self) -> String {
self.bucket_name.clone()
}
async fn write_storage_guid(&self) -> Result<()> {
let guid_str = self.guid().to_string();
let guid_bytes = guid_str.as_bytes();
let bucket = self.get_bucket()?;
let response = bucket
.put_object(
format!("{}/{}", self.storage_prefix(), XVC_STORAGE_GUID_FILENAME),
guid_bytes,
)
.await;
match response {
Ok(_) => Ok(()),
Err(err) => Err(Error::S3Error { source: err }),
}
}
fn build_storage_path(&self, cache_path: &XvcCachePath) -> XvcStoragePath {
XvcStoragePath::from(format!(
"{}/{}/{}/{}",
self.bucket_name(),
self.storage_prefix(),
self.guid(),
cache_path
))
}
fn region(&self) -> String {
self.region.clone()
}
}