ci / rust (pull_request) Successful in 4m13s
The shared section is the implicit global scope, so its fields use the bare `MAESTRO_<FIELD>` env-var spelling. Only `[daemon]` and `[registry]` sections get section-prefixed env vars. Renames: - MAESTRO_SHARED_TOKEN → MAESTRO_TOKEN - MAESTRO_SHARED_REGISTRY_URL → MAESTRO_REGISTRY_URL `MAESTRO_DAEMON_*` and `MAESTRO_REGISTRY_BIND_ADDR` are unchanged. This restores the canonical spec name (`MAESTRO_TOKEN`) that operators already use in their deployment scripts — NAP-233 had renamed it to `MAESTRO_SHARED_TOKEN`, which silently broke daemon auth against existing registry deployments still setting `MAESTRO_TOKEN` in env. Constants renamed alongside: `ENV_SHARED_TOKEN` → `ENV_TOKEN`, `ENV_SHARED_REGISTRY_URL` → `ENV_REGISTRY_URL`. `EnvOverrides` fields shed their `shared_` prefix to match. Touches: daemon/cli/registry crates, e2e harness, design spec.
173 lines
5.3 KiB
Rust
173 lines
5.3 KiB
Rust
//! CLI view over the unified Maestro config at `~/.config/maestro/config.toml`.
|
|
//!
|
|
//! Reads `[shared].registry_url` (registry HTTP base) and `[cli].cookie`
|
|
//! (persisted `maestro_session` JWT). All other sections in the file are
|
|
//! preserved on write.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use maestro_config::MaestroConfigFile;
|
|
|
|
pub use maestro_config::CONFIG_PATH_ENV;
|
|
|
|
/// `~/.config/maestro/config.toml` (XDG-aware).
|
|
pub fn default_config_path() -> Result<PathBuf, ConfigError> {
|
|
Ok(maestro_config::default_config_path()?)
|
|
}
|
|
|
|
/// `$MAESTRO_CONFIG` if set, else [`default_config_path`].
|
|
pub fn resolve_config_path() -> Result<PathBuf, ConfigError> {
|
|
Ok(maestro_config::resolve_config_path()?)
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ConfigError {
|
|
#[error(transparent)]
|
|
Inner(#[from] maestro_config::ConfigError),
|
|
#[error(
|
|
"no registry URL configured — pass --registry-url, set \
|
|
MAESTRO_REGISTRY_URL, or run `maestro set-registry <url>`"
|
|
)]
|
|
NoRegistryUrl,
|
|
}
|
|
|
|
/// Resolved CLI config — the subset of the unified file that this binary
|
|
/// actually consumes.
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
pub struct CliConfig {
|
|
pub registry_url: Option<String>,
|
|
pub cookie: Option<String>,
|
|
}
|
|
|
|
impl CliConfig {
|
|
/// Load `[shared].registry_url` + `[cli].cookie` from the unified file.
|
|
/// Returns an empty config if the file is missing — the CLI tolerates a
|
|
/// missing file so `maestro set-registry <url>` can bootstrap it.
|
|
pub fn load(path: &Path) -> Result<Self, ConfigError> {
|
|
let file = MaestroConfigFile::load_or_default(path)?;
|
|
Ok(Self {
|
|
registry_url: file.shared.registry_url,
|
|
cookie: file.cli.cookie,
|
|
})
|
|
}
|
|
|
|
/// Persist `[shared].registry_url`. Read-modify-write — preserves
|
|
/// any other sections already in the file (`[daemon]`, `[registry]`,
|
|
/// existing `[cli].cookie`).
|
|
pub fn save_registry_url(path: &Path, url: &str) -> Result<(), ConfigError> {
|
|
let mut file = MaestroConfigFile::load_or_default(path)?;
|
|
file.shared.registry_url = Some(url.to_string());
|
|
file.save(path)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Persist `[cli].cookie`. Same read-modify-write semantics as
|
|
/// [`save_registry_url`]; in particular this does not touch
|
|
/// `[shared].registry_url`.
|
|
pub fn save_cookie(path: &Path, cookie: &str) -> Result<(), ConfigError> {
|
|
let mut file = MaestroConfigFile::load_or_default(path)?;
|
|
file.cli.cookie = Some(cookie.to_string());
|
|
file.save(path)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use maestro_config::SharedSection;
|
|
|
|
fn write_file(path: &Path, body: &str) {
|
|
std::fs::write(path, body).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn missing_file_returns_empty() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let cfg = CliConfig::load(&dir.path().join("config.toml")).unwrap();
|
|
assert_eq!(cfg, CliConfig::default());
|
|
}
|
|
|
|
#[test]
|
|
fn load_reads_shared_registry_url_and_cli_cookie() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("config.toml");
|
|
write_file(
|
|
&path,
|
|
r#"
|
|
[shared]
|
|
registry_url = "https://r.example"
|
|
|
|
[cli]
|
|
cookie = "eyJ..."
|
|
"#,
|
|
);
|
|
let cfg = CliConfig::load(&path).unwrap();
|
|
assert_eq!(cfg.registry_url.as_deref(), Some("https://r.example"));
|
|
assert_eq!(cfg.cookie.as_deref(), Some("eyJ..."));
|
|
}
|
|
|
|
#[test]
|
|
fn save_registry_url_preserves_other_sections() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("config.toml");
|
|
write_file(
|
|
&path,
|
|
r#"
|
|
[shared]
|
|
token = "abc"
|
|
|
|
[daemon]
|
|
daemon_url = "ws://laptop:7070"
|
|
"#,
|
|
);
|
|
CliConfig::save_registry_url(&path, "https://new.example/").unwrap();
|
|
let after = MaestroConfigFile::load(&path).unwrap();
|
|
assert_eq!(
|
|
after.shared.registry_url.as_deref(),
|
|
Some("https://new.example/")
|
|
);
|
|
assert_eq!(after.shared.token.as_deref(), Some("abc"));
|
|
assert_eq!(after.daemon.daemon_url.as_deref(), Some("ws://laptop:7070"));
|
|
}
|
|
|
|
#[test]
|
|
fn save_cookie_does_not_touch_registry_url() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("config.toml");
|
|
write_file(
|
|
&path,
|
|
r#"
|
|
[shared]
|
|
registry_url = "https://r.example"
|
|
"#,
|
|
);
|
|
CliConfig::save_cookie(&path, "eyJ.new").unwrap();
|
|
let after = MaestroConfigFile::load(&path).unwrap();
|
|
assert_eq!(
|
|
after.shared.registry_url.as_deref(),
|
|
Some("https://r.example")
|
|
);
|
|
assert_eq!(after.cli.cookie.as_deref(), Some("eyJ.new"));
|
|
}
|
|
|
|
#[test]
|
|
fn save_creates_file_if_missing() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("nested/config.toml");
|
|
CliConfig::save_registry_url(&path, "https://x").unwrap();
|
|
assert!(path.exists());
|
|
let after = MaestroConfigFile::load(&path).unwrap();
|
|
assert_eq!(
|
|
after,
|
|
MaestroConfigFile {
|
|
shared: SharedSection {
|
|
registry_url: Some("https://x".into()),
|
|
token: None,
|
|
},
|
|
..Default::default()
|
|
}
|
|
);
|
|
}
|
|
}
|