docker_secrets 0.2.0

Returns Docker secrets in Rust
Documentation
use std::env;
use std::fs::{self, File};
use std::io::{self, BufReader, Read};
use std::path::Path;

const DEFAULT_SECRET_PATH: &str = "/run/secrets";

fn get_secrets_path() -> String {
    env::var("DOCKER_SECRETS_DIR").unwrap_or_else(|_| DEFAULT_SECRET_PATH.to_string())
}

/// Get Docker secrets list
///
/// Returns a list of secret names found in the secrets directory.
/// Uses `DOCKER_SECRETS_DIR` environment variable if set, otherwise defaults to `/run/secrets`.
pub fn get_list() -> io::Result<Vec<String>> {
    let secret_path = get_secrets_path();
    let path = Path::new(&secret_path);

    let entries = fs::read_dir(path)?;

    let list: Vec<String> = entries
        .filter_map(|entry| entry.ok().and_then(|e| e.file_name().into_string().ok()))
        .collect();

    Ok(list)
}

/// Get a Docker secret by name
///
/// Returns the content of the secret.
/// If the secret does not exist or cannot be read, returns an error.
pub fn get(secret_name: &str) -> io::Result<String> {
    let secret_path = get_secrets_path();
    let path = Path::new(&secret_path).join(secret_name);

    let file = File::open(path)?;
    let mut buf_reader = BufReader::new(file);
    let mut contents = String::new();

    buf_reader.read_to_string(&mut contents)?;

    Ok(contents.trim().to_string())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::Write;
    use std::sync::Mutex;
    use tempfile::TempDir;

    static TEST_MUTEX: Mutex<()> = Mutex::new(());

    // Helper to setup temp secrets dir and return a lock to potential races
    fn setup_secrets() -> (TempDir, std::sync::MutexGuard<'static, ()>) {
        let guard = TEST_MUTEX.lock().unwrap();
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        unsafe {
            env::set_var("DOCKER_SECRETS_DIR", temp_dir.path());
        }
        (temp_dir, guard)
    }

    #[test]
    fn test_get_list() {
        let (temp_dir, _guard) = setup_secrets();
        let secret1_path = temp_dir.path().join("secret1");
        let secret2_path = temp_dir.path().join("secret2");

        File::create(&secret1_path).unwrap();
        File::create(&secret2_path).unwrap();

        let list = get_list().expect("Failed to get list");
        assert!(list.contains(&"secret1".to_string()));
        assert!(list.contains(&"secret2".to_string()));
        assert_eq!(list.len(), 2);
    }

    #[test]
    fn test_get_secret() {
        let (temp_dir, _guard) = setup_secrets();
        let secret_path = temp_dir.path().join("my_secret");

        let mut file = File::create(&secret_path).unwrap();
        writeln!(file, "super_secret_value").unwrap();

        let value = get("my_secret").expect("Failed to get secret");
        assert_eq!(value, "super_secret_value");
    }

    #[test]
    fn test_get_non_existent_secret() {
        let (_temp_dir, _guard) = setup_secrets();
        let result = get("non_existent");
        assert!(result.is_err());
    }
}