xianyun_cluster/kubeconfig/
file.rs1use thiserror::Error;
5
6pub const BACKUP_SUFFIX: &str = ".caproni.bak";
8pub const FILE_MODE: u32 = 0o600;
9pub const DIR_MODE: u32 = 0o700;
10
11#[derive(Debug, Error)]
13pub enum FileError {
14 #[error("getting home dir: {0}")]
15 HomeDir(String),
16 #[error("reading kubeconfig {0}: {1}")]
17 Read(String, String),
18 #[error("creating kubeconfig directory {0}: {1}")]
19 CreateDir(String, String),
20 #[error("creating kubeconfig tempfile: {0}")]
21 CreateTempfile(String),
22 #[error("writing kubeconfig tempfile: {0}")]
23 WriteTempfile(String),
24 #[error("closing kubeconfig tempfile: {0}")]
25 CloseTempfile(String),
26 #[error("setting kubeconfig permissions: {0}")]
27 SetPermissions(String),
28 #[error("replacing kubeconfig {0}: {1}")]
29 Replace(String, String),
30 #[error("writing kubeconfig backup {0}: {1}")]
31 WriteBackup(String, String),
32}
33
34pub fn target_path(override_path: &str) -> Result<String, FileError> {
39 if !override_path.is_empty() {
40 return Ok(override_path.to_string());
41 }
42
43 let sep = if cfg!(windows) { ';' } else { ':' };
44 if let Ok(kubeconfig) = std::env::var("KUBECONFIG") {
45 for path in kubeconfig.split(sep) {
46 if !path.is_empty() {
47 return Ok(path.to_string());
48 }
49 }
50 }
51
52 let home_dir = std::env::var_os("HOME")
53 .or_else(|| std::env::var_os("USERPROFILE"))
54 .map(std::path::PathBuf::from)
55 .ok_or_else(|| FileError::HomeDir("could not determine home directory".to_string()))?;
56
57 Ok(home_dir
58 .join(".kube")
59 .join("config")
60 .to_string_lossy()
61 .into_owned())
62}
63
64pub fn read(path: &str) -> Result<Option<Vec<u8>>, FileError> {
67 match std::fs::read(path) {
68 Ok(data) => Ok(Some(data)),
69 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
70 Err(e) => Err(FileError::Read(path.to_string(), e.to_string())),
71 }
72}
73
74pub fn write(path: &str, content: &[u8]) -> Result<String, FileError> {
77 let path_buf = std::path::Path::new(path);
78 let dir = path_buf
79 .parent()
80 .unwrap_or_else(|| std::path::Path::new("."));
81
82 std::fs::create_dir_all(dir)
83 .map_err(|e| FileError::CreateDir(dir.to_string_lossy().into_owned(), e.to_string()))?;
84
85 let backup_path = backup(path)?;
86
87 write_atomic(path, content, target_mode(path))?;
88
89 Ok(backup_path)
90}
91
92fn target_mode(path: &str) -> u32 {
94 use std::os::unix::fs::PermissionsExt;
95
96 std::fs::metadata(path)
97 .map(|m| m.permissions().mode() & 0o777)
98 .unwrap_or(FILE_MODE)
99}
100
101fn write_atomic(path: &str, content: &[u8], mode: u32) -> Result<(), FileError> {
103 use std::io::Write;
104 use std::os::unix::fs::PermissionsExt;
105
106 let path_buf = std::path::Path::new(path);
107 let dir = path_buf
108 .parent()
109 .unwrap_or_else(|| std::path::Path::new("."));
110
111 let mut tmp = tempfile::Builder::new()
112 .prefix(".caproni-kubeconfig-")
113 .tempfile_in(dir)
114 .map_err(|e| FileError::CreateTempfile(e.to_string()))?;
115
116 tmp.write_all(content)
117 .map_err(|e| FileError::WriteTempfile(e.to_string()))?;
118
119 tmp.as_file()
120 .set_permissions(std::fs::Permissions::from_mode(mode))
121 .map_err(|e| FileError::SetPermissions(e.to_string()))?;
122
123 tmp.persist(path_buf)
124 .map_err(|e| FileError::Replace(path.to_string(), e.to_string()))?;
125
126 Ok(())
127}
128
129fn backup(path: &str) -> Result<String, FileError> {
131 let Some(data) = read(path)? else {
132 return Ok(String::new());
133 };
134
135 let backup_path = format!("{path}{BACKUP_SUFFIX}");
136
137 std::fs::write(&backup_path, &data)
138 .map_err(|e| FileError::WriteBackup(backup_path.clone(), e.to_string()))?;
139
140 Ok(backup_path)
141}