config generation: slight cli ux improvements
This commit is contained in:
@@ -90,6 +90,9 @@ pub struct InitConfigArgs {
|
|||||||
#[arg(long = "config-file", alias = "config")]
|
#[arg(long = "config-file", alias = "config")]
|
||||||
pub config_file: Option<PathBuf>,
|
pub config_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
pub from_config: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(long, conflicts_with = "config_file")]
|
#[arg(long, conflicts_with = "config_file")]
|
||||||
pub stdout: bool,
|
pub stdout: bool,
|
||||||
|
|
||||||
@@ -126,8 +129,8 @@ pub struct InitConfigArgs {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub telemetry_service_name: Option<String>,
|
pub telemetry_service_name: Option<String>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
|
||||||
pub telemetry_json_logs: bool,
|
pub telemetry_json_logs: Option<bool>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
@@ -238,6 +241,7 @@ pub struct StateStatsArgs {
|
|||||||
pub target: AdminTargetArgs,
|
pub target: AdminTargetArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not the greatest thing ive used, this and the migration code gets deleted shortly anyways
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct ImportSledStateArgs {
|
pub struct ImportSledStateArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::cli::{InitConfigArgs, ServeArgs};
|
use crate::cli::{InitConfigArgs, ServeArgs};
|
||||||
use crate::config::resolve::{normalize_public_url, resolve_path};
|
use crate::config::resolve::{load_file_config, normalize_public_url, resolve_path};
|
||||||
use crate::config::types::{WritableConfig, WritableTelemetry};
|
use crate::config::types::{WritableConfig, WritableTelemetry};
|
||||||
|
|
||||||
pub fn write_init_config(args: &InitConfigArgs) -> Result<Option<PathBuf>> {
|
pub fn write_init_config(args: &InitConfigArgs) -> Result<Option<PathBuf>> {
|
||||||
@@ -21,56 +21,104 @@ pub fn write_init_config(args: &InitConfigArgs) -> Result<Option<PathBuf>> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bind = args.bind.unwrap_or_else(|| {
|
let base = if let Some(from_config) = &args.from_config {
|
||||||
"0.0.0.0:8080"
|
if !from_config.exists() {
|
||||||
.parse()
|
anyhow::bail!("base config {} does not exist", from_config.display());
|
||||||
.expect("static default bind should parse")
|
}
|
||||||
});
|
load_file_config(from_config)?
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
let base_telemetry = base.telemetry.unwrap_or_default();
|
||||||
|
|
||||||
|
let bind = match args.bind {
|
||||||
|
Some(bind) => bind,
|
||||||
|
None => match base.bind {
|
||||||
|
Some(ref bind) => bind.parse().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"invalid bind address `{bind}` in base config {}",
|
||||||
|
args.from_config
|
||||||
|
.as_ref()
|
||||||
|
.map(|path| path.display().to_string())
|
||||||
|
.unwrap_or_else(|| "<defaults>".to_string())
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
None => "0.0.0.0:8080"
|
||||||
|
.parse()
|
||||||
|
.expect("static default bind should parse"),
|
||||||
|
},
|
||||||
|
};
|
||||||
let public_url = normalize_public_url(
|
let public_url = normalize_public_url(
|
||||||
args.public_url
|
args.public_url
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
.or(base.public_url.as_deref())
|
||||||
.unwrap_or("http://127.0.0.1:8080"),
|
.unwrap_or("http://127.0.0.1:8080"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let data_dir = args
|
let data_dir = args
|
||||||
.data_dir
|
.data_dir
|
||||||
.clone()
|
.clone()
|
||||||
|
.or(base.data_dir)
|
||||||
.unwrap_or_else(|| PathBuf::from(crate::cli::DEFAULT_DATA_DIR));
|
.unwrap_or_else(|| PathBuf::from(crate::cli::DEFAULT_DATA_DIR));
|
||||||
|
|
||||||
let state_file_raw = args
|
let state_file_raw = args
|
||||||
.state_file
|
.state_file
|
||||||
.clone()
|
.clone()
|
||||||
|
.or(base.state_file)
|
||||||
.unwrap_or_else(|| PathBuf::from("state.sqlite3"));
|
.unwrap_or_else(|| PathBuf::from("state.sqlite3"));
|
||||||
let state_file = resolve_path(&data_dir, state_file_raw);
|
let state_file = resolve_path(&data_dir, state_file_raw);
|
||||||
|
|
||||||
let pid_file_raw = args
|
let pid_file_raw = args
|
||||||
.pid_file
|
.pid_file
|
||||||
.clone()
|
.clone()
|
||||||
|
.or(base.pid_file)
|
||||||
.unwrap_or_else(|| PathBuf::from("wakey-control-plane.pid"));
|
.unwrap_or_else(|| PathBuf::from("wakey-control-plane.pid"));
|
||||||
let pid_file = resolve_path(&data_dir, pid_file_raw);
|
let pid_file = resolve_path(&data_dir, pid_file_raw);
|
||||||
|
|
||||||
let ui_dist_dir = args
|
let ui_dist_dir = args
|
||||||
.ui_dist_dir
|
.ui_dist_dir
|
||||||
.clone()
|
.clone()
|
||||||
|
.or(base.ui_dist_dir)
|
||||||
.unwrap_or_else(|| PathBuf::from("ui/dist"));
|
.unwrap_or_else(|| PathBuf::from("ui/dist"));
|
||||||
|
|
||||||
|
let bootstrap_enroll_tokens = if args.bootstrap_enroll_tokens.is_empty() {
|
||||||
|
base.bootstrap_enroll_tokens.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
args.bootstrap_enroll_tokens.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let body = WritableConfig {
|
let body = WritableConfig {
|
||||||
data_dir,
|
data_dir,
|
||||||
bind: bind.to_string(),
|
bind: bind.to_string(),
|
||||||
public_url,
|
public_url,
|
||||||
state_file,
|
state_file,
|
||||||
command_timeout_ms: args.command_timeout_ms.unwrap_or(30_000).max(1),
|
command_timeout_ms: args
|
||||||
enroll_token_ttl_seconds: args.enroll_token_ttl_seconds.unwrap_or(86_400).max(1),
|
.command_timeout_ms
|
||||||
|
.or(base.command_timeout_ms)
|
||||||
|
.unwrap_or(30_000)
|
||||||
|
.max(1),
|
||||||
|
enroll_token_ttl_seconds: args
|
||||||
|
.enroll_token_ttl_seconds
|
||||||
|
.or(base.enroll_token_ttl_seconds)
|
||||||
|
.unwrap_or(86_400)
|
||||||
|
.max(1),
|
||||||
pid_file,
|
pid_file,
|
||||||
ui_dist_dir,
|
ui_dist_dir,
|
||||||
bootstrap_enroll_tokens: args.bootstrap_enroll_tokens.clone(),
|
bootstrap_enroll_tokens,
|
||||||
telemetry: WritableTelemetry {
|
telemetry: WritableTelemetry {
|
||||||
otlp_endpoint: args.telemetry_otlp_endpoint.clone(),
|
otlp_endpoint: args
|
||||||
|
.telemetry_otlp_endpoint
|
||||||
|
.clone()
|
||||||
|
.or(base_telemetry.otlp_endpoint),
|
||||||
service_name: args
|
service_name: args
|
||||||
.telemetry_service_name
|
.telemetry_service_name
|
||||||
.clone()
|
.clone()
|
||||||
|
.or(base_telemetry.service_name)
|
||||||
.unwrap_or_else(|| "wakey-control-plane".to_string()),
|
.unwrap_or_else(|| "wakey-control-plane".to_string()),
|
||||||
json_logs: args.telemetry_json_logs,
|
json_logs: args
|
||||||
|
.telemetry_json_logs
|
||||||
|
.or(base_telemetry.json_logs)
|
||||||
|
.unwrap_or(false),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,12 +156,93 @@ pub fn bootstrap_config_if_missing(args: &ServeArgs) -> Result<bool> {
|
|||||||
command_timeout_ms: args.command_timeout_ms,
|
command_timeout_ms: args.command_timeout_ms,
|
||||||
enroll_token_ttl_seconds: args.enroll_token_ttl_seconds,
|
enroll_token_ttl_seconds: args.enroll_token_ttl_seconds,
|
||||||
bootstrap_enroll_tokens: args.bootstrap_enroll_tokens.clone(),
|
bootstrap_enroll_tokens: args.bootstrap_enroll_tokens.clone(),
|
||||||
|
from_config: None,
|
||||||
telemetry_otlp_endpoint: None,
|
telemetry_otlp_endpoint: None,
|
||||||
telemetry_service_name: None,
|
telemetry_service_name: None,
|
||||||
telemetry_json_logs: false,
|
telemetry_json_logs: None,
|
||||||
force: false,
|
force: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
write_init_config(&init)?;
|
write_init_config(&init)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::write_init_config;
|
||||||
|
use crate::cli::InitConfigArgs;
|
||||||
|
|
||||||
|
fn temp_test_dir(name: &str) -> std::path::PathBuf {
|
||||||
|
std::env::temp_dir().join(format!(
|
||||||
|
"wakey-cc-init-config-{name}-{}",
|
||||||
|
std::process::id()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_config_from_config_preserves_base_and_applies_overrides() {
|
||||||
|
let dir = temp_test_dir("from-config");
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
std::fs::create_dir_all(&dir).expect("create temp dir");
|
||||||
|
let base = dir.join("base.toml");
|
||||||
|
let out = dir.join("out.toml");
|
||||||
|
std::fs::write(
|
||||||
|
&base,
|
||||||
|
r#"
|
||||||
|
data_dir = "/tmp/wakey-base"
|
||||||
|
bind = "127.0.0.1:9090"
|
||||||
|
public_url = "https://old.example/"
|
||||||
|
state_file = "old.sqlite3"
|
||||||
|
command_timeout_ms = 1234
|
||||||
|
enroll_token_ttl_seconds = 5678
|
||||||
|
pid_file = "old.pid"
|
||||||
|
ui_dist_dir = "/opt/wakey/ui"
|
||||||
|
bootstrap_enroll_tokens = ["old-token"]
|
||||||
|
|
||||||
|
[telemetry]
|
||||||
|
otlp_endpoint = "http://otel.example"
|
||||||
|
service_name = "old-service"
|
||||||
|
json_logs = true
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("write base config");
|
||||||
|
|
||||||
|
write_init_config(&InitConfigArgs {
|
||||||
|
config_file: Some(out.clone()),
|
||||||
|
from_config: Some(base),
|
||||||
|
stdout: false,
|
||||||
|
data_dir: None,
|
||||||
|
bind: None,
|
||||||
|
public_url: Some("https://new.example/".to_string()),
|
||||||
|
state_file: None,
|
||||||
|
pid_file: None,
|
||||||
|
ui_dist_dir: None,
|
||||||
|
command_timeout_ms: Some(999),
|
||||||
|
enroll_token_ttl_seconds: None,
|
||||||
|
bootstrap_enroll_tokens: Vec::new(),
|
||||||
|
telemetry_otlp_endpoint: None,
|
||||||
|
telemetry_service_name: None,
|
||||||
|
telemetry_json_logs: None,
|
||||||
|
force: false,
|
||||||
|
})
|
||||||
|
.expect("write derived config");
|
||||||
|
|
||||||
|
let rendered = std::fs::read_to_string(&out).expect("read derived config");
|
||||||
|
let parsed: toml::Value = toml::from_str(&rendered).expect("parse derived config");
|
||||||
|
assert_eq!(parsed["public_url"].as_str(), Some("https://new.example"));
|
||||||
|
assert_eq!(parsed["bind"].as_str(), Some("127.0.0.1:9090"));
|
||||||
|
assert_eq!(parsed["command_timeout_ms"].as_integer(), Some(999));
|
||||||
|
assert_eq!(parsed["enroll_token_ttl_seconds"].as_integer(), Some(5678));
|
||||||
|
assert_eq!(
|
||||||
|
parsed["state_file"].as_str(),
|
||||||
|
Some("/tmp/wakey-base/old.sqlite3")
|
||||||
|
);
|
||||||
|
assert_eq!(parsed["telemetry"]["json_logs"].as_bool(), Some(true));
|
||||||
|
assert_eq!(
|
||||||
|
parsed["telemetry"]["service_name"].as_str(),
|
||||||
|
Some("old-service")
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user