kftray-commons 0.27.16

KFtray commons
Documentation
use std::{
    env,
    fs,
    path::PathBuf,
};

use anyhow::Result;

pub fn create_config_dir() -> Result<(), Box<dyn std::error::Error>> {
    let config_dir = get_config_dir()?;
    fs::create_dir_all(config_dir)?;
    Ok(())
}

pub fn get_config_dir() -> Result<PathBuf, String> {
    if let Ok(config_dir) = env::var("KFTRAY_CONFIG") {
        return Ok(PathBuf::from(config_dir));
    }

    if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") {
        let mut path = PathBuf::from(xdg_config_home);
        path.push("kftray");
        return Ok(path);
    }

    if let Some(home_dir) = dirs::home_dir() {
        let mut path = home_dir;
        path.push(".kftray");
        return Ok(path);
    }

    Err("Unable to determine the configuration directory".to_string())
}

pub fn get_log_folder_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("http_logs");
    Ok(config_path)
}

pub fn get_db_file_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("configs.db");
    Ok(config_path)
}

pub fn get_pod_manifest_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("proxy_manifest.json");
    Ok(config_path)
}

pub fn get_proxy_deployment_manifest_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("proxy_deployment.json");
    Ok(config_path)
}

pub fn get_expose_deployment_manifest_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("expose_deployment.json");
    Ok(config_path)
}

pub fn get_expose_service_manifest_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("expose_service.json");
    Ok(config_path)
}

pub fn get_expose_ingress_manifest_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("expose_ingress.json");
    Ok(config_path)
}

pub fn get_app_log_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("app.log");
    Ok(config_path)
}

pub fn get_window_state_path() -> Result<PathBuf, String> {
    let mut config_path = get_config_dir()?;
    config_path.push("window_position.json");
    Ok(config_path)
}

pub fn get_kubeconfig_paths() -> Result<Vec<PathBuf>> {
    let mut paths = Vec::new();

    if let Ok(kubeconfig_paths) = env::var("KUBECONFIG") {
        for path in kubeconfig_paths.split(if cfg!(windows) { ';' } else { ':' }) {
            let path_buf = PathBuf::from(path);
            if path_buf.exists() {
                paths.push(path_buf);
            }
        }
    }

    if paths.is_empty()
        && let Some(mut config_path) = dirs::home_dir()
    {
        config_path.push(".kube/config");
        if config_path.exists() {
            paths.push(config_path);
        }
    }

    if paths.is_empty() {
        Err(anyhow::anyhow!("Unable to determine kubeconfig path"))
    } else {
        Ok(paths)
    }
}

#[cfg(test)]
mod tests {
    use std::env;
    use std::fs;
    use std::path::PathBuf;
    use std::sync::Mutex;

    use lazy_static::lazy_static;
    use tempfile::TempDir;

    use super::*;

    lazy_static! {
        static ref ENV_TEST_MUTEX: Mutex<()> = Mutex::new(());
    }

    struct EnvVarGuard {
        key: String,
        original_value: Option<String>,
    }

    impl EnvVarGuard {
        fn set(key: &str, value: &str) -> Self {
            let key = key.to_string();
            let original_value = env::var(&key).ok();
            unsafe { env::set_var(&key, value) };
            EnvVarGuard {
                key,
                original_value,
            }
        }

        fn remove(key: &str) -> Self {
            let key = key.to_string();
            let original_value = env::var(&key).ok();
            unsafe { env::remove_var(&key) };
            EnvVarGuard {
                key,
                original_value,
            }
        }
    }

    impl Drop for EnvVarGuard {
        fn drop(&mut self) {
            match &self.original_value {
                Some(val) => unsafe { env::set_var(&self.key, val) },

                None => unsafe { env::remove_var(&self.key) },
            }
        }
    }

    #[test]
    fn test_get_config_dir_kftray_var() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/config/dir");
        let config_dir = get_config_dir().unwrap();
        assert_eq!(config_dir, PathBuf::from("/custom/config/dir"));
    }

    #[test]
    fn test_get_config_dir_xdg_var() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard1 = EnvVarGuard::remove("KFTRAY_CONFIG");
        let _guard2 = EnvVarGuard::set("XDG_CONFIG_HOME", "/xdg/config/home");
        let config_dir = get_config_dir().unwrap();
        let expected_path = PathBuf::from("/xdg/config/home/kftray");
        assert_eq!(config_dir, expected_path);
    }

    #[test]
    fn test_get_config_dir_default_home() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let temp_dir = tempfile::TempDir::new().unwrap();
        let fake_home = temp_dir.path().to_str().unwrap();

        let _guard1 = EnvVarGuard::remove("KFTRAY_CONFIG");
        let _guard2 = EnvVarGuard::remove("XDG_CONFIG_HOME");
        let _guard3 = EnvVarGuard::set("HOME", fake_home);

        let config_dir = get_config_dir().unwrap();
        let expected_path = PathBuf::from(fake_home).join(".kftray");
        assert_eq!(config_dir, expected_path);
    }

    #[test]
    fn test_get_config_dir_multiple_fallbacks() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let temp_dir = tempfile::TempDir::new().unwrap();
        let fake_xdg = temp_dir.path().join("xdg").to_str().unwrap().to_string();
        let fake_home = temp_dir.path().join("home").to_str().unwrap().to_string();

        let _guard1 = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/path");
        let _guard2 = EnvVarGuard::set("XDG_CONFIG_HOME", &fake_xdg);
        let _guard3 = EnvVarGuard::set("HOME", &fake_home);
        assert_eq!(get_config_dir().unwrap(), PathBuf::from("/custom/path"));

        let _guard4 = EnvVarGuard::remove("KFTRAY_CONFIG");
        assert_eq!(
            get_config_dir().unwrap(),
            PathBuf::from(&fake_xdg).join("kftray")
        );
    }

    #[test]
    fn test_get_log_folder_path() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/config/dir");
        let log_folder_path = get_log_folder_path().unwrap();
        assert_eq!(
            log_folder_path,
            PathBuf::from("/custom/config/dir/http_logs")
        );
    }

    #[test]
    fn test_get_db_file_path() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/config/dir");
        let db_file_path = get_db_file_path().unwrap();
        assert_eq!(db_file_path, PathBuf::from("/custom/config/dir/configs.db"));
    }

    #[test]
    fn test_get_pod_manifest_path() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/config/dir");
        let pod_manifest_path = get_pod_manifest_path().unwrap();
        assert_eq!(
            pod_manifest_path,
            PathBuf::from("/custom/config/dir/proxy_manifest.json")
        );
    }

    #[test]
    fn test_get_app_log_path() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/config/dir");
        let app_log_path = get_app_log_path().unwrap();
        assert_eq!(app_log_path, PathBuf::from("/custom/config/dir/app.log"));
    }

    #[test]
    fn test_get_window_state_path() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard = EnvVarGuard::set("KFTRAY_CONFIG", "/custom/config/dir");
        let window_state_path = get_window_state_path().unwrap();
        assert_eq!(
            window_state_path,
            PathBuf::from("/custom/config/dir/window_position.json")
        );
    }

    #[test]
    fn test_get_kubeconfig_paths() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let temp_dir = TempDir::new().unwrap();

        let fake_home_dir = temp_dir.path().join("fake_home");
        fs::create_dir_all(&fake_home_dir).unwrap();
        let _guard_home = EnvVarGuard::set("HOME", fake_home_dir.to_str().unwrap());

        let custom_kubeconfig_path = temp_dir.path().join("custom_kube_config");
        fs::write(&custom_kubeconfig_path, "mock kubeconfig content").unwrap();

        let _guard_kube = EnvVarGuard::set("KUBECONFIG", custom_kubeconfig_path.to_str().unwrap());
        let kubeconfig_paths = get_kubeconfig_paths().unwrap();
        assert_eq!(kubeconfig_paths, vec![custom_kubeconfig_path.clone()]);

        let _guard_kube2 = EnvVarGuard::remove("KUBECONFIG");
        let kube_dir = fake_home_dir.join(".kube");
        fs::create_dir_all(&kube_dir).unwrap();
        let expected_default_path = kube_dir.join("config");
        fs::write(&expected_default_path, "mock kubeconfig content").unwrap();

        let kubeconfig_paths = get_kubeconfig_paths().unwrap();
        assert_eq!(kubeconfig_paths, vec![expected_default_path]);
    }

    #[test]
    fn test_get_kubeconfig_paths_with_multiple_paths() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let temp_dir = TempDir::new().unwrap();
        let config1 = temp_dir.path().join("config1");
        let config2 = temp_dir.path().join("config2");

        fs::write(&config1, "kubeconfig1").unwrap();
        fs::write(&config2, "kubeconfig2").unwrap();

        let separator = if cfg!(windows) { ";" } else { ":" };
        let kubeconfig_env = format!(
            "{}{}{}",
            config1.to_str().unwrap(),
            separator,
            config2.to_str().unwrap()
        );

        let _guard = EnvVarGuard::set("KUBECONFIG", &kubeconfig_env);

        let paths = get_kubeconfig_paths().unwrap();
        assert_eq!(paths.len(), 2);
        assert!(paths.contains(&config1));
        assert!(paths.contains(&config2));
    }

    #[test]
    fn test_get_kubeconfig_paths_error() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let temp_dir = TempDir::new().unwrap();
        let home_path = temp_dir.path().to_str().unwrap();
        let _guard_home = EnvVarGuard::set("HOME", home_path);
        let _guard_kube = EnvVarGuard::remove("KUBECONFIG");

        let kube_dir = temp_dir.path().join(".kube");
        if kube_dir.exists() {
            let config_path = kube_dir.join("config");
            if config_path.exists() {
                std::fs::remove_file(config_path).unwrap();
            }
        }

        let result = get_kubeconfig_paths();
        assert!(
            result.is_err(),
            "get_kubeconfig_paths() should return error when no config exists"
        );

        let error_msg = result.unwrap_err().to_string();
        assert_eq!(error_msg, "Unable to determine kubeconfig path");
    }

    #[test]
    fn test_get_config_dir_using_home_dir_fallback() {
        let _lock = ENV_TEST_MUTEX.lock().unwrap();
        let _guard_kftray = EnvVarGuard::remove("KFTRAY_CONFIG");
        let _guard_xdg = EnvVarGuard::remove("XDG_CONFIG_HOME");

        if let Ok(_config_dir) = get_config_dir() {
            assert!(get_log_folder_path().is_ok());
            assert!(get_db_file_path().is_ok());
            assert!(get_pod_manifest_path().is_ok());
            assert!(get_app_log_path().is_ok());
            assert!(get_window_state_path().is_ok());
        } else {
            assert!(get_config_dir().is_err());
        }
    }
}