🛰️ export from upstream (6885be6)
This commit is contained in:
@@ -69,8 +69,8 @@ pub async fn handle(
|
|||||||
let global_config = home.join(".sandcage").join("config.toml");
|
let global_config = home.join(".sandcage").join("config.toml");
|
||||||
let config_src = crate::config_source::resolve_config_source(&workspace);
|
let config_src = crate::config_source::resolve_config_source(&workspace);
|
||||||
let config_root = config_src.as_ref().map(|s| s.root.clone()).unwrap_or_else(|| workspace.clone());
|
let config_root = config_src.as_ref().map(|s| s.root.clone()).unwrap_or_else(|| workspace.clone());
|
||||||
let project_config = config_root.join(".sandcage").join("sandcage.yml");
|
let project_config = config_root.join(".sandcage").join("config.toml");
|
||||||
let local_config = config_root.join(".sandcage").join("local").join("sandcage.local.yml");
|
let local_config = config_root.join(".sandcage").join("local").join("config.local.toml");
|
||||||
let cfg = crate::config::resolve_config(
|
let cfg = crate::config::resolve_config(
|
||||||
Some(&global_config),
|
Some(&global_config),
|
||||||
Some(&project_config),
|
Some(&project_config),
|
||||||
|
|||||||
+28
-274
@@ -6,7 +6,7 @@ use thiserror::Error;
|
|||||||
|
|
||||||
use figment::providers::Format as _;
|
use figment::providers::Format as _;
|
||||||
|
|
||||||
use crate::config::{SandcageConfig, SshKeyEntry};
|
use crate::config::SandcageConfig;
|
||||||
use crate::ssh_config::{self, SshHostBlock};
|
use crate::ssh_config::{self, SshHostBlock};
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
@@ -19,11 +19,11 @@ pub enum SetupError {
|
|||||||
#[diagnostic(code(sandcage::setup::read_dir_failed))]
|
#[diagnostic(code(sandcage::setup::read_dir_failed))]
|
||||||
ReadDirFailed(PathBuf, #[source] std::io::Error),
|
ReadDirFailed(PathBuf, #[source] std::io::Error),
|
||||||
|
|
||||||
#[error("No .sandcage/sandcage.yml found. Run `sandcage init` first, use `--global`, or set SANDCAGE_CONFIG_DIR.")]
|
#[error("No .sandcage/config.toml found. Run `sandcage init` first, use `--global`, or set SANDCAGE_CONFIG_DIR.")]
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(sandcage::setup::no_project_config),
|
code(sandcage::setup::no_project_config),
|
||||||
help("In a git worktree, .sandcage/ is loaded from the main worktree root. \
|
help("In a git worktree, .sandcage/ is loaded from the main worktree root. \
|
||||||
You can also use a redirect file (sandcage.yml with redirect: key).")
|
You can also use a redirect file (config.toml with redirect key).")
|
||||||
)]
|
)]
|
||||||
NoProjectConfig,
|
NoProjectConfig,
|
||||||
|
|
||||||
@@ -104,115 +104,6 @@ pub fn check_existing_mount(config: &SandcageConfig, mount: &str) -> bool {
|
|||||||
.is_some_and(|m| m.iter().any(|entry| entry == mount))
|
.is_some_and(|m| m.iter().any(|entry| entry == mount))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_mount_to_yaml(path: &Path, mount: &str) -> Result<()> {
|
|
||||||
let content = std::fs::read_to_string(path)
|
|
||||||
.map_err(|e| SetupError::ConfigReadFailed(path.to_path_buf(), e))?;
|
|
||||||
|
|
||||||
let mut config: SandcageConfig = if content.trim().is_empty() {
|
|
||||||
SandcageConfig::default()
|
|
||||||
} else {
|
|
||||||
serde_yaml::from_str(&content)
|
|
||||||
.map_err(|e| SetupError::ConfigParseFailed(path.to_path_buf(), e.to_string()))?
|
|
||||||
};
|
|
||||||
|
|
||||||
let mounts = config.mounts.get_or_insert_with(Vec::new);
|
|
||||||
mounts.push(mount.to_string());
|
|
||||||
|
|
||||||
let yaml = serde_yaml::to_string(&config)
|
|
||||||
.map_err(|e| SetupError::ConfigParseFailed(path.to_path_buf(), e.to_string()))?;
|
|
||||||
|
|
||||||
let output = rehydrate_yaml_comments(&content, &yaml);
|
|
||||||
|
|
||||||
std::fs::write(path, output)
|
|
||||||
.map_err(|e| SetupError::ConfigWriteFailed(path.to_path_buf(), e))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rehydrate_yaml_comments(original: &str, generated: &str) -> String {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
let mut key_comments: Vec<(Option<String>, Vec<String>)> = Vec::new();
|
|
||||||
let mut pending: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
for line in original.lines() {
|
|
||||||
let trimmed = line.trim();
|
|
||||||
let is_blank = trimmed.is_empty();
|
|
||||||
let is_top_comment =
|
|
||||||
trimmed.starts_with('#') && !line.starts_with(' ') && !line.starts_with('\t');
|
|
||||||
let is_top_level_key = !is_blank
|
|
||||||
&& !is_top_comment
|
|
||||||
&& !line.starts_with(' ')
|
|
||||||
&& !line.starts_with('\t')
|
|
||||||
&& !line.starts_with('-')
|
|
||||||
&& line.contains(':');
|
|
||||||
|
|
||||||
if is_top_level_key {
|
|
||||||
let key = line.split(':').next().unwrap().trim().to_string();
|
|
||||||
key_comments.push((Some(key), std::mem::take(&mut pending)));
|
|
||||||
} else if is_blank || is_top_comment {
|
|
||||||
pending.push(line.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !pending.is_empty() {
|
|
||||||
key_comments.push((None, pending));
|
|
||||||
}
|
|
||||||
|
|
||||||
let comment_map: HashMap<&str, &[String]> = key_comments
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(k, v)| k.as_deref().map(|k| (k, v.as_slice())))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let preamble = key_comments
|
|
||||||
.first()
|
|
||||||
.and_then(|(k, v)| if k.is_some() { Some(v.as_slice()) } else { None });
|
|
||||||
|
|
||||||
let trailer = key_comments
|
|
||||||
.last()
|
|
||||||
.and_then(|(k, v)| if k.is_none() { Some(v.as_slice()) } else { None });
|
|
||||||
|
|
||||||
let mut result = String::new();
|
|
||||||
let mut first_key = true;
|
|
||||||
|
|
||||||
for line in generated.lines() {
|
|
||||||
let trimmed = line.trim();
|
|
||||||
let is_top_level_key = !trimmed.is_empty()
|
|
||||||
&& !line.starts_with(' ')
|
|
||||||
&& !line.starts_with('\t')
|
|
||||||
&& !line.starts_with('-')
|
|
||||||
&& line.contains(':');
|
|
||||||
|
|
||||||
if is_top_level_key {
|
|
||||||
let key = line.split(':').next().unwrap().trim();
|
|
||||||
if first_key {
|
|
||||||
if let Some(pre) = preamble {
|
|
||||||
for c in pre {
|
|
||||||
result.push_str(c);
|
|
||||||
result.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_key = false;
|
|
||||||
} else if let Some(comments) = comment_map.get(key) {
|
|
||||||
for c in *comments {
|
|
||||||
result.push_str(c);
|
|
||||||
result.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.push_str(line);
|
|
||||||
result.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(trail) = trailer {
|
|
||||||
for c in trail {
|
|
||||||
result.push_str(c);
|
|
||||||
result.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_mount_to_toml(path: &Path, mount: &str) -> Result<()> {
|
pub fn add_mount_to_toml(path: &Path, mount: &str) -> Result<()> {
|
||||||
let content = if path.exists() {
|
let content = if path.exists() {
|
||||||
std::fs::read_to_string(path)
|
std::fs::read_to_string(path)
|
||||||
@@ -345,54 +236,6 @@ pub fn has_legacy_ssh_mount(config: &SandcageConfig) -> bool {
|
|||||||
config.ssh_mode.is_none() && check_existing_mount(config, SSH_MOUNT)
|
config.ssh_mode.is_none() && check_existing_mount(config, SSH_MOUNT)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_ssh_config_to_yaml(path: &Path, entries: &[SshDiscoveryEntry]) -> Result<()> {
|
|
||||||
let content = if path.exists() {
|
|
||||||
std::fs::read_to_string(path)
|
|
||||||
.map_err(|e| SetupError::ConfigReadFailed(path.to_path_buf(), e))?
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut config: SandcageConfig = if content.trim().is_empty() {
|
|
||||||
SandcageConfig::default()
|
|
||||||
} else {
|
|
||||||
serde_yaml::from_str(&content)
|
|
||||||
.map_err(|e| SetupError::ConfigParseFailed(path.to_path_buf(), e.to_string()))?
|
|
||||||
};
|
|
||||||
|
|
||||||
config.ssh_mode = Some("volume".to_string());
|
|
||||||
config.ssh_keys = Some(
|
|
||||||
entries
|
|
||||||
.iter()
|
|
||||||
.map(|e| SshKeyEntry {
|
|
||||||
host: e
|
|
||||||
.ssh_host_alias
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| e.display_host.clone()),
|
|
||||||
identity_file: e.identity_file.clone().unwrap_or_default(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove old bind mount if present
|
|
||||||
if let Some(ref mut mounts) = config.mounts {
|
|
||||||
mounts.retain(|m| m != SSH_MOUNT);
|
|
||||||
if mounts.is_empty() {
|
|
||||||
config.mounts = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let yaml = serde_yaml::to_string(&config)
|
|
||||||
.map_err(|e| SetupError::ConfigParseFailed(path.to_path_buf(), e.to_string()))?;
|
|
||||||
|
|
||||||
let output = rehydrate_yaml_comments(&content, &yaml);
|
|
||||||
|
|
||||||
std::fs::write(path, output)
|
|
||||||
.map_err(|e| SetupError::ConfigWriteFailed(path.to_path_buf(), e))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_ssh_config_to_toml(path: &Path, entries: &[SshDiscoveryEntry]) -> Result<()> {
|
pub fn write_ssh_config_to_toml(path: &Path, entries: &[SshDiscoveryEntry]) -> Result<()> {
|
||||||
let content = if path.exists() {
|
let content = if path.exists() {
|
||||||
std::fs::read_to_string(path)
|
std::fs::read_to_string(path)
|
||||||
@@ -455,7 +298,7 @@ pub fn run_ssh_setup(global: bool, yes: bool, refresh: bool, bind: bool) -> Resu
|
|||||||
} else {
|
} else {
|
||||||
let cwd = std::env::current_dir()
|
let cwd = std::env::current_dir()
|
||||||
.map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
.map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
||||||
cwd.join(".sandcage").join("sandcage.yml")
|
cwd.join(".sandcage").join("config.toml")
|
||||||
};
|
};
|
||||||
|
|
||||||
if config_path_for_check.exists() {
|
if config_path_for_check.exists() {
|
||||||
@@ -478,31 +321,15 @@ pub fn run_ssh_setup(global: bool, yes: bool, refresh: bool, bind: bool) -> Resu
|
|||||||
.interact()
|
.interact()
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !migrate {
|
if !migrate {
|
||||||
// Set explicit bind mode so we don't ask again
|
let mut doc: toml_edit::DocumentMut =
|
||||||
if global {
|
std::fs::read_to_string(&config_path_for_check)
|
||||||
let mut doc: toml_edit::DocumentMut =
|
.unwrap_or_default()
|
||||||
std::fs::read_to_string(&config_path_for_check)
|
.parse()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default();
|
||||||
.parse()
|
doc["ssh_mode"] = toml_edit::value("bind");
|
||||||
.unwrap_or_default();
|
std::fs::write(&config_path_for_check, doc.to_string()).map_err(
|
||||||
doc["ssh_mode"] = toml_edit::value("bind");
|
|e| SetupError::ConfigWriteFailed(config_path_for_check, e),
|
||||||
std::fs::write(&config_path_for_check, doc.to_string()).map_err(
|
)?;
|
||||||
|e| SetupError::ConfigWriteFailed(config_path_for_check, e),
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
let content = std::fs::read_to_string(&config_path_for_check)
|
|
||||||
.map_err(|e| {
|
|
||||||
SetupError::ConfigReadFailed(config_path_for_check.clone(), e)
|
|
||||||
})?;
|
|
||||||
let mut cfg: SandcageConfig =
|
|
||||||
serde_yaml::from_str(&content).unwrap_or_default();
|
|
||||||
cfg.ssh_mode = Some("bind".to_string());
|
|
||||||
let yaml = serde_yaml::to_string(&cfg).unwrap_or_default();
|
|
||||||
let output = rehydrate_yaml_comments(&content, &yaml);
|
|
||||||
std::fs::write(&config_path_for_check, output).map_err(|e| {
|
|
||||||
SetupError::ConfigWriteFailed(config_path_for_check, e)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
eprintln!("sandcage: keeping bind mount, set ssh_mode: bind");
|
eprintln!("sandcage: keeping bind mount, set ssh_mode: bind");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -602,7 +429,7 @@ pub fn run_ssh_setup(global: bool, yes: bool, refresh: bool, bind: bool) -> Resu
|
|||||||
} else {
|
} else {
|
||||||
let cwd = std::env::current_dir()
|
let cwd = std::env::current_dir()
|
||||||
.map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
.map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
||||||
(cwd.join(".sandcage").join("local").join("sandcage.local.yml"), ".sandcage/local/sandcage.local.yml", false)
|
(cwd.join(".sandcage").join("local").join("config.local.toml"), ".sandcage/local/config.local.toml", false)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write config
|
// Write config
|
||||||
@@ -613,7 +440,7 @@ pub fn run_ssh_setup(global: bool, yes: bool, refresh: bool, bind: bool) -> Resu
|
|||||||
}
|
}
|
||||||
write_ssh_config_to_toml(&config_path, &selected_entries)?;
|
write_ssh_config_to_toml(&config_path, &selected_entries)?;
|
||||||
} else {
|
} else {
|
||||||
write_ssh_config_to_yaml(&config_path, &selected_entries)?;
|
write_ssh_config_to_toml(&config_path, &selected_entries)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate volume
|
// Populate volume
|
||||||
@@ -628,13 +455,19 @@ pub fn run_ssh_setup(global: bool, yes: bool, refresh: bool, bind: bool) -> Resu
|
|||||||
|
|
||||||
fn run_ssh_refresh(global: bool, ssh_dir: &Path) -> Result<()> {
|
fn run_ssh_refresh(global: bool, ssh_dir: &Path) -> Result<()> {
|
||||||
let home = dirs::home_dir().ok_or(SetupError::NoHomeDir)?;
|
let home = dirs::home_dir().ok_or(SetupError::NoHomeDir)?;
|
||||||
|
let global_config = home.join(".sandcage").join("config.toml");
|
||||||
|
|
||||||
let config_path = if global {
|
let config_path = if global {
|
||||||
home.join(".sandcage").join("config.toml")
|
global_config.clone()
|
||||||
} else {
|
} else {
|
||||||
let cwd = std::env::current_dir()
|
let cwd = std::env::current_dir()
|
||||||
.map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
.map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
||||||
cwd.join(".sandcage").join("local").join("sandcage.local.yml")
|
let local = cwd.join(".sandcage").join("local").join("config.local.toml");
|
||||||
|
if local.exists() {
|
||||||
|
local
|
||||||
|
} else {
|
||||||
|
global_config.clone()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !config_path.exists() {
|
if !config_path.exists() {
|
||||||
@@ -644,7 +477,7 @@ fn run_ssh_refresh(global: bool, ssh_dir: &Path) -> Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cfg = if global {
|
let cfg = {
|
||||||
let figment = figment::Figment::from(
|
let figment = figment::Figment::from(
|
||||||
figment::providers::Serialized::defaults(SandcageConfig::default()),
|
figment::providers::Serialized::defaults(SandcageConfig::default()),
|
||||||
)
|
)
|
||||||
@@ -652,9 +485,6 @@ fn run_ssh_refresh(global: bool, ssh_dir: &Path) -> Result<()> {
|
|||||||
figment
|
figment
|
||||||
.extract::<SandcageConfig>()
|
.extract::<SandcageConfig>()
|
||||||
.map_err(|e| SetupError::ConfigParseFailed(config_path.clone(), e.to_string()))?
|
.map_err(|e| SetupError::ConfigParseFailed(config_path.clone(), e.to_string()))?
|
||||||
} else {
|
|
||||||
crate::config::load(&config_path)
|
|
||||||
.map_err(|e| SetupError::ConfigParseFailed(config_path.clone(), e.to_string()))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let ssh_keys = cfg.ssh_keys.unwrap_or_default();
|
let ssh_keys = cfg.ssh_keys.unwrap_or_default();
|
||||||
@@ -727,11 +557,11 @@ fn run_ssh_setup_bind(global: bool, yes: bool) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
let cwd =
|
let cwd =
|
||||||
std::env::current_dir().map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
std::env::current_dir().map_err(|e| SetupError::ReadDirFailed(PathBuf::from("."), e))?;
|
||||||
let project_config = cwd.join(".sandcage").join("sandcage.yml");
|
let project_config = cwd.join(".sandcage").join("config.toml");
|
||||||
if !project_config.exists() {
|
if !project_config.exists() {
|
||||||
return Err(SetupError::NoProjectConfig);
|
return Err(SetupError::NoProjectConfig);
|
||||||
}
|
}
|
||||||
(project_config, ".sandcage/sandcage.yml".to_string())
|
(project_config, ".sandcage/config.toml".to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if already configured
|
// Check if already configured
|
||||||
@@ -783,7 +613,7 @@ fn run_ssh_setup_bind(global: bool, yes: bool) -> Result<()> {
|
|||||||
}
|
}
|
||||||
add_mount_to_toml(&config_path, SSH_MOUNT)?;
|
add_mount_to_toml(&config_path, SSH_MOUNT)?;
|
||||||
} else {
|
} else {
|
||||||
add_mount_to_yaml(&config_path, SSH_MOUNT)?;
|
add_mount_to_toml(&config_path, SSH_MOUNT)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("sandcage: added SSH mount to {config_label}");
|
eprintln!("sandcage: added SSH mount to {config_label}");
|
||||||
@@ -1018,8 +848,7 @@ pub fn populate_ssh_volume(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::io::Write;
|
use tempfile::TempDir;
|
||||||
use tempfile::{NamedTempFile, TempDir};
|
|
||||||
|
|
||||||
fn touch(dir: &Path, name: &str) {
|
fn touch(dir: &Path, name: &str) {
|
||||||
std::fs::write(dir.join(name), "").expect("touch file");
|
std::fs::write(dir.join(name), "").expect("touch file");
|
||||||
@@ -1108,81 +937,6 @@ mod tests {
|
|||||||
assert!(!check_existing_mount(&config, "~/.ssh:/home/agent/.ssh:ro"));
|
assert!(!check_existing_mount(&config, "~/.ssh:/home/agent/.ssh:ro"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- add_mount_to_yaml tests --
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_mount_to_yaml_with_existing_mounts() {
|
|
||||||
let yaml = "env:\n FOO: bar\nmounts:\n - /data:/mnt\n";
|
|
||||||
let mut tmp = NamedTempFile::new().unwrap();
|
|
||||||
write!(tmp, "{yaml}").unwrap();
|
|
||||||
|
|
||||||
add_mount_to_yaml(tmp.path(), "~/.ssh:/home/agent/.ssh:ro").unwrap();
|
|
||||||
|
|
||||||
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
|
||||||
assert!(content.contains("~/.ssh:/home/agent/.ssh:ro"));
|
|
||||||
assert!(content.contains("/data:/mnt"));
|
|
||||||
assert!(content.contains("FOO"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_mount_to_yaml_no_existing_mounts() {
|
|
||||||
let yaml = "env:\n FOO: bar\n";
|
|
||||||
let mut tmp = NamedTempFile::new().unwrap();
|
|
||||||
write!(tmp, "{yaml}").unwrap();
|
|
||||||
|
|
||||||
add_mount_to_yaml(tmp.path(), "~/.ssh:/home/agent/.ssh:ro").unwrap();
|
|
||||||
|
|
||||||
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
|
||||||
assert!(content.contains("~/.ssh:/home/agent/.ssh:ro"));
|
|
||||||
assert!(content.contains("FOO"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_mount_to_yaml_empty_file() {
|
|
||||||
let mut tmp = NamedTempFile::new().unwrap();
|
|
||||||
write!(tmp, "").unwrap();
|
|
||||||
|
|
||||||
add_mount_to_yaml(tmp.path(), "~/.ssh:/home/agent/.ssh:ro").unwrap();
|
|
||||||
|
|
||||||
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
|
||||||
assert!(content.contains("~/.ssh:/home/agent/.ssh:ro"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_mount_to_yaml_preserves_comments() {
|
|
||||||
let yaml = "# Environment\nenv:\n FOO: bar\n\n# Mount points\nmounts:\n- /data:/mnt\n";
|
|
||||||
let mut tmp = NamedTempFile::new().unwrap();
|
|
||||||
write!(tmp, "{yaml}").unwrap();
|
|
||||||
|
|
||||||
add_mount_to_yaml(tmp.path(), "~/.ssh:/home/agent/.ssh:ro").unwrap();
|
|
||||||
|
|
||||||
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
|
||||||
assert!(content.contains("# Environment"), "preamble comment preserved");
|
|
||||||
assert!(content.contains("# Mount points"), "inline comment preserved");
|
|
||||||
assert!(content.contains("~/.ssh:/home/agent/.ssh:ro"));
|
|
||||||
assert!(content.contains("/data:/mnt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_mount_to_yaml_preserves_trailing_comments() {
|
|
||||||
let yaml = "mounts:\n- /data:/mnt\n\n# end of file\n";
|
|
||||||
let mut tmp = NamedTempFile::new().unwrap();
|
|
||||||
write!(tmp, "{yaml}").unwrap();
|
|
||||||
|
|
||||||
add_mount_to_yaml(tmp.path(), "~/.ssh:/home/agent/.ssh:ro").unwrap();
|
|
||||||
|
|
||||||
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
|
||||||
assert!(content.contains("# end of file"), "trailing comment preserved");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rehydrate_preserves_blank_lines_between_keys() {
|
|
||||||
let original = "env:\n A: 1\n\nmounts:\n- /data:/mnt\n";
|
|
||||||
let generated = "env:\n A: '1'\nmounts:\n- /data:/mnt\n- /new:/new\n";
|
|
||||||
let result = rehydrate_yaml_comments(original, generated);
|
|
||||||
assert!(result.contains("\n\nmounts:"), "blank line before mounts preserved");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- add_mount_to_toml tests --
|
// -- add_mount_to_toml tests --
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user