1#![warn(missing_docs)]
15#![allow(clippy::redundant_closure)]
16
17use backend::Backend;
18use lazy_static::lazy_static;
19use log::trace;
20use std::cell::RefCell;
21use std::convert::AsRef;
22use std::default::Default;
23use std::ops::DerefMut;
24use std::path::Path;
25use std::sync::{Mutex, MutexGuard};
26
27pub use errors::{Error, Result};
31pub use secretfile::{Secretfile, SecretfileKeys};
32
33mod backend;
34mod chained;
35mod envvar;
36mod errors;
37mod secretfile;
38mod vault;
39
40pub struct Options {
42 secretfile: Option<Secretfile>,
43 allow_override: bool,
44}
45
46impl Default for Options {
47 fn default() -> Options {
50 Options {
51 secretfile: None,
52 allow_override: true,
53 }
54 }
55}
56
57impl Options {
58 pub fn secretfile(mut self, secretfile: Secretfile) -> Options {
62 self.secretfile = Some(secretfile);
63 self
64 }
65
66 pub fn allow_override(mut self, allow_override: bool) -> Options {
69 self.allow_override = allow_override;
70 self
71 }
72}
73
74pub struct Client {
79 secretfile: Secretfile,
80 backend: chained::Client,
81}
82
83impl Client {
84 pub fn new(options: Options) -> Result<Client> {
86 let secretfile = match options.secretfile {
87 Some(sf) => sf,
88 None => Secretfile::default()?,
89 };
90 let over = options.allow_override;
91 Ok(Client {
92 secretfile,
93 backend: chained::Client::with_default_backends(over)?,
94 })
95 }
96
97 pub fn default() -> Result<Client> {
99 Client::new(Default::default())
100 }
101
102 pub fn with_secretfile(secretfile: Secretfile) -> Result<Client> {
104 Client::new(Options::default().secretfile(secretfile))
105 }
106
107 pub fn secretfile(&self) -> &Secretfile {
109 &self.secretfile
110 }
111
112 pub fn var<S: AsRef<str>>(&mut self, name: S) -> Result<String> {
114 let name_ref = name.as_ref();
115 trace!("getting secure credential {}", name_ref);
116 self.backend
117 .var(&self.secretfile, name_ref)
118 .map_err(|err| Error::Credential {
119 name: name_ref.to_owned(),
120 cause: Box::new(err),
121 })
122 }
123
124 pub fn file<S: AsRef<Path>>(&mut self, path: S) -> Result<String> {
126 let path_ref = path.as_ref();
127 let path_str = path_ref.to_str().ok_or_else(|| Error::Credential {
128 name: format!("{}", path_ref.display()),
129 cause: Box::new(Error::NonUnicodePath {
130 path: path_ref.to_owned(),
131 }),
132 })?;
133 trace!("getting secure credential {}", path_str);
134 self.backend
135 .file(&self.secretfile, path_str)
136 .map_err(|err| Error::Credential {
137 name: path_str.to_owned(),
138 cause: Box::new(err),
139 })
140 }
141}
142
143lazy_static! {
144 static ref CLIENT: Mutex<RefCell<Option<Client>>> =
155 Mutex::new(RefCell::new(None));
156}
157
158#[allow(clippy::let_and_return)] fn with_client<F>(body: F) -> Result<String>
162where
163 F: FnOnce(&mut Client) -> Result<String>,
164{
165 let client_cell: MutexGuard<'_, _> = CLIENT.lock().unwrap();
166
167 if client_cell.borrow().is_none() {
169 *client_cell.borrow_mut() = Some(Client::default()?);
170 }
171
172 let result = match client_cell.borrow_mut().deref_mut() {
176 Some(client) => body(client),
177 None => panic!("Should have a client, but we don't"),
180 };
181 result
182}
183
184pub fn var<S: AsRef<str>>(name: S) -> Result<String> {
186 with_client(|client| client.var(name))
187}
188
189pub fn file<S: AsRef<Path>>(path: S) -> Result<String> {
191 with_client(|client| client.file(path))
192}
193
194#[cfg(test)]
195mod test {
196 use super::file;
197 use std::fs;
198 use std::io::Read;
199
200 #[test]
201 fn test_file() {
202 let mut f = fs::File::open("Cargo.toml").unwrap();
204 let mut expected = String::new();
205 f.read_to_string(&mut expected).unwrap();
206
207 assert_eq!(expected, file("Cargo.toml").unwrap());
208 assert!(file("nosuchfile.txt").is_err());
209 }
210}