Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/kubeconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ where
users: vec![],
};

let mut load_errors: Vec<String> = vec![];

for path in kubeconfigs.into_iter() {
let path = path.as_ref();

Expand All @@ -282,11 +284,19 @@ where
.extend(kubeconfig.users.drain(..).map(|x| Sourced::new(&path, x)));
}
Err(err) => {
eprintln!("Error loading kubeconfig {}: {}", path.display(), err);
load_errors.push(format!("Error loading kubeconfig {}: {}", path.display(), err));
}
}
}

// Only print load errors when no contexts were found, to avoid
// noisy warnings about broken files when valid configs exist.
if installed.contexts.is_empty() {
for err in &load_errors {
eprintln!("{}", err);
}
}

Ok(installed)
}

Expand Down
175 changes: 164 additions & 11 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,47 @@ pub struct Settings {
pub fzf: Fzf,
}

/// Check if a path has a kubie settings filename.
fn is_kubie_settings_name(path: &Path) -> bool {
matches!(path.file_name().and_then(|f| f.to_str()), Some("kubie.yaml") | Some("kubie.yml"))
}

/// Parse KUBECONFIG into individual entries (colon-separated on unix).
fn parse_kubeconfig_env() -> Vec<PathBuf> {
match std::env::var("KUBECONFIG") {
Ok(val) if !val.is_empty() => val.split(':').map(PathBuf::from).collect(),
_ => vec![],
}
}

/// Find a kubie settings file (kubie.yaml or kubie.yml) in a directory.
fn find_settings_in_dir(dir: &Path) -> Option<String> {
["kubie.yaml", "kubie.yml"].iter().find_map(|name| {
let c = dir.join(name);
if c.is_file() { c.to_str().map(String::from) } else { None }
})
}

impl Settings {
pub fn path() -> String {
for entry in parse_kubeconfig_env() {
if is_kubie_settings_name(&entry) && entry.is_file() {
if let Some(s) = entry.to_str() {
return s.to_string();
}
}
if let Some(s) = find_settings_in_dir(&entry) {
return s;
}
}

let xdg_config = std::env::var("XDG_CONFIG_HOME")
.unwrap_or_else(|_| format!("{}/.config", home_dir()));
let xdg_dir = Path::new(&xdg_config).join("kubie");
if let Some(s) = find_settings_in_dir(&xdg_dir) {
return s;
}

format!("{}/.kube/kubie.yaml", home_dir())
}

Expand All @@ -74,23 +113,37 @@ impl Settings {
Settings::default()
};

// Very important to exclude kubie's own config file ~/.kube/kubie.yaml from the results.
// Very important to exclude kubie's own config file from the results.
settings.configs.exclude.push(settings_path_str);
Ok(settings)
}

pub fn get_kube_configs_paths(&self) -> Result<HashSet<PathBuf>> {
let mut paths = HashSet::new();

for entry in parse_kubeconfig_env() {
if entry.is_file() && !is_kubie_settings_name(&entry) {
paths.insert(entry);
} else if entry.is_dir() {
for pattern in &["*.yml", "*.yaml"] {
for matched in glob(&format!("{}/{pattern}", entry.display()))? {
let path = matched?;
if !is_kubie_settings_name(&path) {
paths.insert(path);
}
}
}
}
}

for inc in &self.configs.include {
let expanded = expanduser(inc);
for entry in glob(&expanded)? {
for entry in glob(&expanduser(inc))? {
paths.insert(entry?);
}
}

for exc in &self.configs.exclude {
let expanded = expanduser(exc);
for entry in glob(&expanded)? {
for entry in glob(&expanduser(exc))? {
paths.remove(&entry?);
}
}
Expand Down Expand Up @@ -224,10 +277,110 @@ fn def_bool_false() -> bool {
false
}

#[test]
fn test_expanduser() {
assert_eq!(
expanduser("~/hello/world/*.foo"),
format!("{}/hello/world/*.foo", home_dir())
);
#[cfg(test)]
mod tests {
use super::*;
use std::fs;

#[test]
fn test_expanduser() {
assert_eq!(
expanduser("~/hello/world/*.foo"),
format!("{}/hello/world/*.foo", home_dir())
);
}

#[test]
fn test_kubeconfig_env_file() {
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join("test-cluster.yaml");
fs::write(&file_path, "apiVersion: v1").unwrap();

std::env::set_var("KUBECONFIG", file_path.to_str().unwrap());

let settings = Settings {
configs: Configs {
include: vec![],
exclude: vec![],
},
..Settings::default()
};

let paths = settings.get_kube_configs_paths().unwrap();
assert!(paths.contains(&file_path));

std::env::remove_var("KUBECONFIG");
}

#[test]
fn test_kubeconfig_env_directory() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("a.yaml"), "apiVersion: v1").unwrap();
fs::write(dir.path().join("b.yml"), "apiVersion: v1").unwrap();
fs::write(dir.path().join("c.txt"), "not a kubeconfig").unwrap();
fs::write(dir.path().join("kubie.yaml"), "configs: {}").unwrap();

std::env::set_var("KUBECONFIG", dir.path().to_str().unwrap());

let settings = Settings {
configs: Configs {
include: vec![],
exclude: vec![],
},
..Settings::default()
};

let paths = settings.get_kube_configs_paths().unwrap();
assert!(paths.contains(&dir.path().join("a.yaml")));
assert!(paths.contains(&dir.path().join("b.yml")));
assert!(!paths.contains(&dir.path().join("c.txt")));
assert!(!paths.contains(&dir.path().join("kubie.yaml")));

std::env::remove_var("KUBECONFIG");
}

#[test]
fn test_kubeconfig_env_picks_up_kubie_settings() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("kubie.yaml"), "configs: {}").unwrap();

std::env::set_var("KUBECONFIG", dir.path().to_str().unwrap());

let path = Settings::path();
assert_eq!(path, dir.path().join("kubie.yaml").to_str().unwrap());

std::env::remove_var("KUBECONFIG");
}

#[test]
fn test_kubeconfig_env_unset() {
std::env::remove_var("KUBECONFIG");

let settings = Settings {
configs: Configs {
include: vec![],
exclude: vec![],
},
..Settings::default()
};

let paths = settings.get_kube_configs_paths().unwrap();
assert!(paths.is_empty());
}

#[test]
fn test_xdg_config_home() {
std::env::remove_var("KUBECONFIG");
let dir = tempfile::tempdir().unwrap();
let kubie_dir = dir.path().join("kubie");
fs::create_dir_all(&kubie_dir).unwrap();
fs::write(kubie_dir.join("kubie.yaml"), "configs: {}").unwrap();

std::env::set_var("XDG_CONFIG_HOME", dir.path().to_str().unwrap());

let path = Settings::path();
assert_eq!(path, kubie_dir.join("kubie.yaml").to_str().unwrap());

std::env::remove_var("XDG_CONFIG_HOME");
}
}