#![warn(missing_docs)]
#![allow(clippy::redundant_closure)]
use backend::Backend;
use lazy_static::lazy_static;
use log::trace;
use std::cell::RefCell;
use std::convert::AsRef;
use std::default::Default;
use std::ops::DerefMut;
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
pub use errors::{Error, Result};
pub use secretfile::{Secretfile, SecretfileKeys};
mod backend;
mod chained;
mod envvar;
mod errors;
mod secretfile;
mod vault;
pub struct Options {
secretfile: Option<Secretfile>,
allow_override: bool,
}
impl Default for Options {
fn default() -> Options {
Options {
secretfile: None,
allow_override: true,
}
}
}
impl Options {
pub fn secretfile(mut self, secretfile: Secretfile) -> Options {
self.secretfile = Some(secretfile);
self
}
pub fn allow_override(mut self, allow_override: bool) -> Options {
self.allow_override = allow_override;
self
}
}
pub struct Client {
secretfile: Secretfile,
backend: chained::Client,
}
impl Client {
pub fn new(options: Options) -> Result<Client> {
let secretfile = match options.secretfile {
Some(sf) => sf,
None => Secretfile::default()?,
};
let over = options.allow_override;
Ok(Client {
secretfile,
backend: chained::Client::with_default_backends(over)?,
})
}
pub fn default() -> Result<Client> {
Client::new(Default::default())
}
pub fn with_secretfile(secretfile: Secretfile) -> Result<Client> {
Client::new(Options::default().secretfile(secretfile))
}
pub fn secretfile(&self) -> &Secretfile {
&self.secretfile
}
pub fn var<S: AsRef<str>>(&mut self, name: S) -> Result<String> {
let name_ref = name.as_ref();
trace!("getting secure credential {}", name_ref);
self.backend
.var(&self.secretfile, name_ref)
.map_err(|err| Error::Credential {
name: name_ref.to_owned(),
cause: Box::new(err),
})
}
pub fn file<S: AsRef<Path>>(&mut self, path: S) -> Result<String> {
let path_ref = path.as_ref();
let path_str = path_ref.to_str().ok_or_else(|| Error::Credential {
name: format!("{}", path_ref.display()),
cause: Box::new(Error::NonUnicodePath {
path: path_ref.to_owned(),
}),
})?;
trace!("getting secure credential {}", path_str);
self.backend
.file(&self.secretfile, path_str)
.map_err(|err| Error::Credential {
name: path_str.to_owned(),
cause: Box::new(err),
})
}
}
lazy_static! {
static ref CLIENT: Mutex<RefCell<Option<Client>>> =
Mutex::new(RefCell::new(None));
}
#[allow(clippy::let_and_return)] fn with_client<F>(body: F) -> Result<String>
where
F: FnOnce(&mut Client) -> Result<String>,
{
let client_cell: MutexGuard<'_, _> = CLIENT.lock().unwrap();
if client_cell.borrow().is_none() {
*client_cell.borrow_mut() = Some(Client::default()?);
}
let result = match client_cell.borrow_mut().deref_mut() {
Some(client) => body(client),
None => panic!("Should have a client, but we don't"),
};
result
}
pub fn var<S: AsRef<str>>(name: S) -> Result<String> {
with_client(|client| client.var(name))
}
pub fn file<S: AsRef<Path>>(path: S) -> Result<String> {
with_client(|client| client.file(path))
}
#[cfg(test)]
mod test {
use super::file;
use std::fs;
use std::io::Read;
#[test]
fn test_file() {
let mut f = fs::File::open("Cargo.toml").unwrap();
let mut expected = String::new();
f.read_to_string(&mut expected).unwrap();
assert_eq!(expected, file("Cargo.toml").unwrap());
assert!(file("nosuchfile.txt").is_err());
}
}